diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 7367ee6..6473995 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -634,6 +634,16 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { palam.RoleRegistry = roleRegistry palam.Lex = lex + // Create Pons (Inter-Government Bridge) contract + pons := newPons() + pons.NEO = neo + pons.Vita = vita + pons.Federation = federation + pons.RoleRegistry = roleRegistry + pons.VTS = vts + pons.Scire = scire + pons.Salus = salus + return []interop.Contract{ mgmt, s, @@ -658,5 +668,6 @@ func NewDefaultContracts(cfg config.ProtocolConfiguration) []interop.Contract { tribute, opus, palam, + pons, } } diff --git a/pkg/core/native/native_test/management_test.go b/pkg/core/native/native_test/management_test.go index b6534e6..e2795f9 100644 --- a/pkg/core/native/native_test/management_test.go +++ b/pkg/core/native/native_test/management_test.go @@ -62,6 +62,7 @@ var ( nativenames.Tribute: `{"id":-21,"hash":"0x1e49af098f224f28b080ad526d73e3b551bf9b35","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1171474237},"manifest":{"name":"Tribute","abi":{"methods":[{"name":"appealTribute","offset":0,"parameters":[{"name":"assessmentID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"assessTribute","offset":7,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":false},{"name":"claimIncentive","offset":14,"parameters":[{"name":"incentiveID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"collectTribute","offset":21,"parameters":[{"name":"assessmentID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"createVelocityAccount","offset":28,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"getAccount","offset":35,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getAccountByVitaID","offset":42,"parameters":[{"name":"vitaID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getAssessment","offset":49,"parameters":[{"name":"assessmentID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getConfig","offset":56,"parameters":[],"returntype":"Array","safe":true},{"name":"getHoardingLevel","offset":63,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"getIncentive","offset":70,"parameters":[{"name":"incentiveID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getPendingAssessment","offset":77,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getRedistribution","offset":84,"parameters":[{"name":"redistID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getTotalAccounts","offset":91,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalAssessments","offset":98,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalIncentives","offset":105,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalRedistributions","offset":112,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTributePool","offset":119,"parameters":[],"returntype":"Integer","safe":true},{"name":"getUnclaimedIncentives","offset":126,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"getVelocity","offset":133,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"grantExemption","offset":140,"parameters":[{"name":"owner","type":"Hash160"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"grantIncentive","offset":147,"parameters":[{"name":"owner","type":"Hash160"},{"name":"incentiveType","type":"Integer"},{"name":"amount","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Integer","safe":false},{"name":"isExempt","offset":154,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"recordTransaction","offset":161,"parameters":[{"name":"owner","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"isOutflow","type":"Boolean"}],"returntype":"Boolean","safe":false},{"name":"redistribute","offset":168,"parameters":[{"name":"targetCategory","type":"String"},{"name":"recipientCount","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"revokeExemption","offset":175,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"waiveTribute","offset":182,"parameters":[{"name":"assessmentID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false}],"events":[{"name":"VelocityAccountCreated","parameters":[{"name":"vitaID","type":"Integer"},{"name":"owner","type":"Hash160"}]},{"name":"VelocityUpdated","parameters":[{"name":"vitaID","type":"Integer"},{"name":"newVelocity","type":"Integer"},{"name":"hoardingLevel","type":"Integer"}]},{"name":"TributeAssessed","parameters":[{"name":"assessmentID","type":"Integer"},{"name":"vitaID","type":"Integer"},{"name":"amount","type":"Integer"}]},{"name":"TributeCollected","parameters":[{"name":"assessmentID","type":"Integer"},{"name":"amount","type":"Integer"}]},{"name":"TributeWaived","parameters":[{"name":"assessmentID","type":"Integer"},{"name":"reason","type":"String"}]},{"name":"TributeAppealed","parameters":[{"name":"assessmentID","type":"Integer"},{"name":"reason","type":"String"}]},{"name":"IncentiveGranted","parameters":[{"name":"incentiveID","type":"Integer"},{"name":"vitaID","type":"Integer"},{"name":"amount","type":"Integer"}]},{"name":"IncentiveClaimed","parameters":[{"name":"incentiveID","type":"Integer"},{"name":"amount","type":"Integer"}]},{"name":"RedistributionExecuted","parameters":[{"name":"redistID","type":"Integer"},{"name":"totalAmount","type":"Integer"},{"name":"recipientCount","type":"Integer"}]},{"name":"ExemptionGranted","parameters":[{"name":"vitaID","type":"Integer"},{"name":"reason","type":"String"}]},{"name":"ExemptionRevoked","parameters":[{"name":"vitaID","type":"Integer"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.Opus: `{"id":-22,"hash":"0xfdb69606e4e5c74708513a7c4dfcab98177322f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":323743677},"manifest":{"name":"Opus","abi":{"methods":[{"name":"addCapability","offset":0,"parameters":[{"name":"workerID","type":"Integer"},{"name":"name","type":"String"},{"name":"category","type":"String"},{"name":"level","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"assignTask","offset":7,"parameters":[{"name":"workerID","type":"Integer"},{"name":"taskType","type":"String"},{"name":"description","type":"String"},{"name":"valueOffered","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"cancelTask","offset":14,"parameters":[{"name":"taskID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"completeTask","offset":21,"parameters":[{"name":"taskID","type":"Integer"},{"name":"resultHash","type":"Hash256"},{"name":"valueCompleted","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"decommissionWorker","offset":28,"parameters":[{"name":"workerID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"failTask","offset":35,"parameters":[{"name":"taskID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"getActiveTask","offset":42,"parameters":[{"name":"workerID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getCapability","offset":49,"parameters":[{"name":"capabilityID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getConfig","offset":56,"parameters":[],"returntype":"Array","safe":true},{"name":"getOperator","offset":63,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Array","safe":true},{"name":"getTask","offset":70,"parameters":[{"name":"taskID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getTotalCapabilities","offset":77,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalOperators","offset":84,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalTasks","offset":91,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalTribute","offset":98,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalWorkers","offset":105,"parameters":[],"returntype":"Integer","safe":true},{"name":"getWorker","offset":112,"parameters":[{"name":"workerID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getWorkerCountByOperator","offset":119,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","safe":true},{"name":"hasCapability","offset":126,"parameters":[{"name":"workerID","type":"Integer"},{"name":"name","type":"String"}],"returntype":"Boolean","safe":true},{"name":"reactivateWorker","offset":133,"parameters":[{"name":"workerID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"registerOperator","offset":140,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":false},{"name":"registerWorker","offset":147,"parameters":[{"name":"name","type":"String"},{"name":"description","type":"String"},{"name":"workerType","type":"Integer"},{"name":"modelHash","type":"Hash256"}],"returntype":"Integer","safe":false},{"name":"setConfig","offset":154,"parameters":[{"name":"minTributeRate","type":"Integer"},{"name":"maxTributeRate","type":"Integer"},{"name":"defaultTributeRate","type":"Integer"},{"name":"registrationFee","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"startTask","offset":161,"parameters":[{"name":"taskID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"suspendWorker","offset":168,"parameters":[{"name":"workerID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"updateWorker","offset":175,"parameters":[{"name":"workerID","type":"Integer"},{"name":"description","type":"String"}],"returntype":"Boolean","safe":false},{"name":"verifyCapability","offset":182,"parameters":[{"name":"capabilityID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"verifyOperator","offset":189,"parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Boolean","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, nativenames.Palam: `{"id":-23,"hash":"0xae16798853df46b0e2fa8c8dab343a71fa732303","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQA==","checksum":1841570703},"manifest":{"name":"Palam","abi":{"methods":[{"name":"approveDeclassify","offset":0,"parameters":[{"name":"requestID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"attachToFlow","offset":7,"parameters":[{"name":"flowID","type":"Hash256"},{"name":"attachmentType","type":"String"},{"name":"encryptedData","type":"ByteArray"}],"returntype":"Integer","safe":false},{"name":"denyDeclassify","offset":14,"parameters":[{"name":"requestID","type":"Integer"},{"name":"reason","type":"String"}],"returntype":"Boolean","safe":false},{"name":"getAccessLog","offset":21,"parameters":[{"name":"logID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getAttachment","offset":28,"parameters":[{"name":"attachmentID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getConfig","offset":35,"parameters":[],"returntype":"Array","safe":true},{"name":"getDeclassifyRequest","offset":42,"parameters":[{"name":"requestID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getFlow","offset":49,"parameters":[{"name":"flowID","type":"Hash256"}],"returntype":"Array","safe":false},{"name":"getRolePermissions","offset":56,"parameters":[{"name":"role","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getTotalAttachments","offset":63,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalFlows","offset":70,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalLogs","offset":77,"parameters":[],"returntype":"Integer","safe":true},{"name":"getTotalRequests","offset":84,"parameters":[],"returntype":"Integer","safe":true},{"name":"hasDeclassifyGrant","offset":91,"parameters":[{"name":"flowID","type":"Hash256"},{"name":"requester","type":"Hash160"}],"returntype":"Boolean","safe":true},{"name":"recordFlow","offset":98,"parameters":[{"name":"tag","type":"String"},{"name":"amount","type":"Integer"},{"name":"bucket","type":"String"},{"name":"participants","type":"Array"},{"name":"consumerData","type":"ByteArray"},{"name":"merchantData","type":"ByteArray"},{"name":"distributorData","type":"ByteArray"},{"name":"producerData","type":"ByteArray"},{"name":"ngoData","type":"ByteArray"},{"name":"auditorData","type":"ByteArray"},{"name":"previousFlowID","type":"Hash256"}],"returntype":"Hash256","safe":false},{"name":"requestDeclassify","offset":105,"parameters":[{"name":"flowID","type":"Hash256"},{"name":"caseID","type":"String"},{"name":"reason","type":"String"},{"name":"expirationBlocks","type":"Integer"}],"returntype":"Integer","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, + nativenames.Pons: `{"id":-24,"hash":"0xccdd81357cc7a7532b809a3f928cb8a519d03958","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1991619121},"manifest":{"name":"Pons","abi":{"methods":[{"name":"cancelSettlement","offset":0,"parameters":[{"name":"settlementID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"completeSettlement","offset":7,"parameters":[{"name":"settlementID","type":"Integer"},{"name":"txHash","type":"Hash256"}],"returntype":"Boolean","safe":false},{"name":"createAgreement","offset":14,"parameters":[{"name":"remoteChainID","type":"Integer"},{"name":"agreementType","type":"Integer"},{"name":"termsHash","type":"Hash256"},{"name":"expirationHeight","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"getAgreement","offset":21,"parameters":[{"name":"agreementID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getAgreementCount","offset":28,"parameters":[],"returntype":"Integer","safe":true},{"name":"getConfig","offset":35,"parameters":[],"returntype":"Array","safe":true},{"name":"getCredentialShare","offset":42,"parameters":[{"name":"shareID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getCredentialShareCount","offset":49,"parameters":[],"returntype":"Integer","safe":true},{"name":"getSettlementCount","offset":56,"parameters":[],"returntype":"Integer","safe":true},{"name":"getSettlementRequest","offset":63,"parameters":[{"name":"settlementID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"getVerificationCount","offset":70,"parameters":[],"returntype":"Integer","safe":true},{"name":"getVerificationRequest","offset":77,"parameters":[{"name":"requestID","type":"Integer"}],"returntype":"Array","safe":true},{"name":"hasActiveAgreement","offset":84,"parameters":[{"name":"remoteChainID","type":"Integer"},{"name":"agreementType","type":"Integer"}],"returntype":"Boolean","safe":true},{"name":"requestSettlement","offset":91,"parameters":[{"name":"toChainID","type":"Integer"},{"name":"receiver","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"reference","type":"String"}],"returntype":"Integer","safe":false},{"name":"requestVerification","offset":98,"parameters":[{"name":"targetChainID","type":"Integer"},{"name":"subject","type":"Hash160"},{"name":"verificationType","type":"Integer"},{"name":"dataHash","type":"Hash256"}],"returntype":"Integer","safe":false},{"name":"respondVerification","offset":105,"parameters":[{"name":"requestID","type":"Integer"},{"name":"approved","type":"Boolean"},{"name":"responseHash","type":"Hash256"}],"returntype":"Boolean","safe":false},{"name":"revokeCredentialShare","offset":112,"parameters":[{"name":"shareID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"setLocalChainID","offset":119,"parameters":[{"name":"chainID","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"shareCredential","offset":126,"parameters":[{"name":"targetChainID","type":"Integer"},{"name":"credentialType","type":"Integer"},{"name":"credentialID","type":"Integer"},{"name":"contentHash","type":"Hash256"},{"name":"validUntil","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"updateAgreementStatus","offset":133,"parameters":[{"name":"agreementID","type":"Integer"},{"name":"newStatus","type":"Integer"}],"returntype":"Boolean","safe":false},{"name":"verifyCredentialShare","offset":140,"parameters":[{"name":"shareID","type":"Integer"}],"returntype":"Boolean","safe":true}],"events":[{"name":"AgreementCreated","parameters":[{"name":"agreementID","type":"Integer"},{"name":"remoteChainID","type":"Integer"},{"name":"agreementType","type":"Integer"}]},{"name":"AgreementUpdated","parameters":[{"name":"agreementID","type":"Integer"},{"name":"oldStatus","type":"Integer"},{"name":"newStatus","type":"Integer"}]},{"name":"AgreementTerminated","parameters":[{"name":"agreementID","type":"Integer"},{"name":"remoteChainID","type":"Integer"}]},{"name":"VerificationRequested","parameters":[{"name":"requestID","type":"Integer"},{"name":"targetChainID","type":"Integer"},{"name":"subject","type":"Hash160"},{"name":"verificationType","type":"Integer"}]},{"name":"VerificationResponded","parameters":[{"name":"requestID","type":"Integer"},{"name":"approved","type":"Boolean"}]},{"name":"SettlementRequested","parameters":[{"name":"settlementID","type":"Integer"},{"name":"toChainID","type":"Integer"},{"name":"receiver","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"SettlementCompleted","parameters":[{"name":"settlementID","type":"Integer"},{"name":"txHash","type":"Hash256"}]},{"name":"CredentialShared","parameters":[{"name":"shareID","type":"Integer"},{"name":"owner","type":"Hash160"},{"name":"targetChainID","type":"Integer"},{"name":"credentialType","type":"Integer"}]},{"name":"CredentialRevoked","parameters":[{"name":"shareID","type":"Integer"},{"name":"owner","type":"Hash160"}]}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null},"updatecounter":0}`, } // cockatriceCSS holds serialized native contract states built for genesis block (with UpdateCounter 0) // under assumption that hardforks from Aspidochelone to Cockatrice (included) are enabled. diff --git a/pkg/core/native/nativehashes/hashes.go b/pkg/core/native/nativehashes/hashes.go index 707dac4..7c610cc 100644 --- a/pkg/core/native/nativehashes/hashes.go +++ b/pkg/core/native/nativehashes/hashes.go @@ -55,4 +55,6 @@ var ( Opus = util.Uint160{0xf5, 0x22, 0x73, 0x17, 0x98, 0xab, 0xfc, 0x4d, 0x7c, 0x3a, 0x51, 0x8, 0x47, 0xc7, 0xe5, 0xe4, 0x6, 0x96, 0xb6, 0xfd} // Palam is a hash of native Palam contract. Palam = util.Uint160{0x3, 0x23, 0x73, 0xfa, 0x71, 0x3a, 0x34, 0xab, 0x8d, 0x8c, 0xfa, 0xe2, 0xb0, 0x46, 0xdf, 0x53, 0x88, 0x79, 0x16, 0xae} + // Pons is a hash of native Pons contract. + Pons = util.Uint160{0x58, 0x39, 0xd0, 0x19, 0xa5, 0xb8, 0x8c, 0x92, 0x3f, 0x9a, 0x80, 0x2b, 0x53, 0xa7, 0xc7, 0x7c, 0x35, 0x81, 0xdd, 0xcc} ) diff --git a/pkg/core/native/nativeids/ids.go b/pkg/core/native/nativeids/ids.go index 62cabef..573d745 100644 --- a/pkg/core/native/nativeids/ids.go +++ b/pkg/core/native/nativeids/ids.go @@ -53,4 +53,6 @@ const ( Opus int32 = -22 // Palam is an ID of native Palam contract. Palam int32 = -23 + // Pons is an ID of native Pons contract. + Pons int32 = -24 ) diff --git a/pkg/core/native/nativenames/names.go b/pkg/core/native/nativenames/names.go index f5dbe9b..dd57807 100644 --- a/pkg/core/native/nativenames/names.go +++ b/pkg/core/native/nativenames/names.go @@ -25,6 +25,7 @@ const ( Tribute = "Tribute" Opus = "Opus" Palam = "Palam" + Pons = "Pons" ) // All contains the list of all native contract names ordered by the contract ID. @@ -52,6 +53,7 @@ var All = []string{ Tribute, Opus, Palam, + Pons, } // IsValid checks if the name is a valid native contract's name. @@ -78,5 +80,6 @@ func IsValid(name string) bool { name == Sese || name == Tribute || name == Opus || - name == Palam + name == Palam || + name == Pons } diff --git a/pkg/core/native/pons.go b/pkg/core/native/pons.go new file mode 100644 index 0000000..a8cb466 --- /dev/null +++ b/pkg/core/native/pons.go @@ -0,0 +1,1260 @@ +package native + +import ( + "encoding/binary" + "errors" + "math/big" + + "github.com/tutus-one/tutus-chain/pkg/config" + "github.com/tutus-one/tutus-chain/pkg/core/dao" + "github.com/tutus-one/tutus-chain/pkg/core/interop" + "github.com/tutus-one/tutus-chain/pkg/core/native/nativeids" + "github.com/tutus-one/tutus-chain/pkg/core/native/nativenames" + "github.com/tutus-one/tutus-chain/pkg/core/state" + "github.com/tutus-one/tutus-chain/pkg/smartcontract" + "github.com/tutus-one/tutus-chain/pkg/smartcontract/callflag" + "github.com/tutus-one/tutus-chain/pkg/smartcontract/manifest" + "github.com/tutus-one/tutus-chain/pkg/util" + "github.com/tutus-one/tutus-chain/pkg/vm/stackitem" +) + +// Pons ("bridge" in Latin) represents the Inter-Government Bridge Protocol +// native contract. It manages: +// - Bilateral agreements between sovereign chains +// - Cross-border verification requests +// - International VTS settlement +// - Education and healthcare credential portability +type Pons struct { + interop.ContractMD + NEO INEO + Vita IVita + Federation *Federation + RoleRegistry *RoleRegistry + VTS *VTS + Scire *Scire + Salus *Salus +} + +// Storage key prefixes for Pons. +const ( + ponsPrefixConfig byte = 0x01 // -> PonsConfig + ponsPrefixAgreement byte = 0x10 // agreementID -> BilateralAgreement + ponsPrefixAgreementByChain byte = 0x11 // chainID + agreementID -> exists + ponsPrefixAgreementCounter byte = 0x1F // -> next agreementID + ponsPrefixVerification byte = 0x20 // requestID -> VerificationRequest + ponsPrefixVerifBySubject byte = 0x21 // subject + requestID -> exists + ponsPrefixVerifCounter byte = 0x2F // -> next verificationID + ponsPrefixSettlement byte = 0x30 // settlementID -> SettlementRequest + ponsPrefixSettlByChain byte = 0x31 // chainID + settlementID -> exists + ponsPrefixSettlCounter byte = 0x3F // -> next settlementID + ponsPrefixCredential byte = 0x40 // credentialID -> CredentialShare + ponsPrefixCredByOwner byte = 0x41 // owner + credentialID -> exists + ponsPrefixCredCounter byte = 0x4F // -> next credentialID +) + +// Default configuration values. +const ( + defaultLocalChainID uint32 = 1 + defaultVerificationTimeout uint32 = 8640 // ~1 day at 10s blocks + defaultSettlementTimeout uint32 = 86400 // ~10 days + defaultMaxPendingRequests uint64 = 10000 + defaultCredentialShareExpiry uint32 = 315360 // ~1 year +) + +// Event names for Pons. +const ( + AgreementCreatedEvent = "AgreementCreated" + AgreementUpdatedEvent = "AgreementUpdated" + AgreementTerminatedEvent = "AgreementTerminated" + VerificationRequestedEvent = "VerificationRequested" + VerificationRespondedEvent = "VerificationResponded" + SettlementRequestedEvent = "SettlementRequested" + SettlementCompletedEvent = "SettlementCompleted" + CredentialSharedEvent = "CredentialShared" + CredentialRevokedEvent = "CredentialRevoked" +) + +// Various errors for Pons. +var ( + ErrNoAgreement = errors.New("no active agreement with target chain") + ErrAgreementExists = errors.New("agreement already exists") + ErrAgreementNotFound = errors.New("agreement not found") + ErrInvalidAgreementType = errors.New("invalid agreement type") + ErrAgreementNotActive = errors.New("agreement not active") + ErrVerificationNotFound = errors.New("verification request not found") + ErrVerificationExpired = errors.New("verification request expired") + ErrSettlementNotFound = errors.New("settlement request not found") + ErrSettlementExpired = errors.New("settlement request expired") + ErrCredentialNotFound = errors.New("credential share not found") + ErrCredentialExpired = errors.New("credential share expired") + ErrCredentialRevoked = errors.New("credential share revoked") + ErrMaxRequestsReached = errors.New("maximum pending requests reached") + ErrNotCredentialOwner = errors.New("not credential owner") + ErrAgreementTypeNotAllowed = errors.New("agreement type not allowed for this operation") +) + +var _ interop.Contract = (*Pons)(nil) + +// newPons creates a new Pons native contract. +func newPons() *Pons { + p := &Pons{ + ContractMD: *interop.NewContractMD(nativenames.Pons, nativeids.Pons), + } + defer p.BuildHFSpecificMD(p.ActiveIn()) + + // getConfig method + desc := NewDescriptor("getConfig", smartcontract.ArrayType) + md := NewMethodAndPrice(p.getConfig, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // setLocalChainID method (committee only) + desc = NewDescriptor("setLocalChainID", smartcontract.BoolType, + manifest.NewParameter("chainID", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.setLocalChainID, 1<<16, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // --- Agreement Management --- + + // createAgreement method (committee only) + desc = NewDescriptor("createAgreement", smartcontract.IntegerType, + manifest.NewParameter("remoteChainID", smartcontract.IntegerType), + manifest.NewParameter("agreementType", smartcontract.IntegerType), + manifest.NewParameter("termsHash", smartcontract.Hash256Type), + manifest.NewParameter("expirationHeight", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.createAgreement, 1<<17, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // updateAgreementStatus method (committee only) + desc = NewDescriptor("updateAgreementStatus", smartcontract.BoolType, + manifest.NewParameter("agreementID", smartcontract.IntegerType), + manifest.NewParameter("newStatus", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.updateAgreementStatus, 1<<16, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // getAgreement method + desc = NewDescriptor("getAgreement", smartcontract.ArrayType, + manifest.NewParameter("agreementID", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.getAgreement, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // hasActiveAgreement method + desc = NewDescriptor("hasActiveAgreement", smartcontract.BoolType, + manifest.NewParameter("remoteChainID", smartcontract.IntegerType), + manifest.NewParameter("agreementType", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.hasActiveAgreement, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // --- Verification Requests --- + + // requestVerification method + desc = NewDescriptor("requestVerification", smartcontract.IntegerType, + manifest.NewParameter("targetChainID", smartcontract.IntegerType), + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("verificationType", smartcontract.IntegerType), + manifest.NewParameter("dataHash", smartcontract.Hash256Type)) + md = NewMethodAndPrice(p.requestVerification, 1<<17, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // respondVerification method (committee only - represents response from other chain) + desc = NewDescriptor("respondVerification", smartcontract.BoolType, + manifest.NewParameter("requestID", smartcontract.IntegerType), + manifest.NewParameter("approved", smartcontract.BoolType), + manifest.NewParameter("responseHash", smartcontract.Hash256Type)) + md = NewMethodAndPrice(p.respondVerification, 1<<16, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // getVerificationRequest method + desc = NewDescriptor("getVerificationRequest", smartcontract.ArrayType, + manifest.NewParameter("requestID", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.getVerificationRequest, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // --- Settlement Requests --- + + // requestSettlement method + desc = NewDescriptor("requestSettlement", smartcontract.IntegerType, + manifest.NewParameter("toChainID", smartcontract.IntegerType), + manifest.NewParameter("receiver", smartcontract.Hash160Type), + manifest.NewParameter("amount", smartcontract.IntegerType), + manifest.NewParameter("reference", smartcontract.StringType)) + md = NewMethodAndPrice(p.requestSettlement, 1<<17, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // completeSettlement method (committee only - represents confirmation from other chain) + desc = NewDescriptor("completeSettlement", smartcontract.BoolType, + manifest.NewParameter("settlementID", smartcontract.IntegerType), + manifest.NewParameter("txHash", smartcontract.Hash256Type)) + md = NewMethodAndPrice(p.completeSettlement, 1<<16, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // cancelSettlement method (sender or committee) + desc = NewDescriptor("cancelSettlement", smartcontract.BoolType, + manifest.NewParameter("settlementID", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.cancelSettlement, 1<<16, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // getSettlementRequest method + desc = NewDescriptor("getSettlementRequest", smartcontract.ArrayType, + manifest.NewParameter("settlementID", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.getSettlementRequest, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // --- Credential Sharing --- + + // shareCredential method + desc = NewDescriptor("shareCredential", smartcontract.IntegerType, + manifest.NewParameter("targetChainID", smartcontract.IntegerType), + manifest.NewParameter("credentialType", smartcontract.IntegerType), + manifest.NewParameter("credentialID", smartcontract.IntegerType), + manifest.NewParameter("contentHash", smartcontract.Hash256Type), + manifest.NewParameter("validUntil", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.shareCredential, 1<<17, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // revokeCredentialShare method + desc = NewDescriptor("revokeCredentialShare", smartcontract.BoolType, + manifest.NewParameter("shareID", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.revokeCredentialShare, 1<<16, callflag.States|callflag.AllowNotify) + p.AddMethod(md, desc) + + // getCredentialShare method + desc = NewDescriptor("getCredentialShare", smartcontract.ArrayType, + manifest.NewParameter("shareID", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.getCredentialShare, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // verifyCredentialShare method + desc = NewDescriptor("verifyCredentialShare", smartcontract.BoolType, + manifest.NewParameter("shareID", smartcontract.IntegerType)) + md = NewMethodAndPrice(p.verifyCredentialShare, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // --- Counter Query Methods --- + + // getAgreementCount method + desc = NewDescriptor("getAgreementCount", smartcontract.IntegerType) + md = NewMethodAndPrice(p.getAgreementCount, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // getVerificationCount method + desc = NewDescriptor("getVerificationCount", smartcontract.IntegerType) + md = NewMethodAndPrice(p.getVerificationCount, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // getSettlementCount method + desc = NewDescriptor("getSettlementCount", smartcontract.IntegerType) + md = NewMethodAndPrice(p.getSettlementCount, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // getCredentialShareCount method + desc = NewDescriptor("getCredentialShareCount", smartcontract.IntegerType) + md = NewMethodAndPrice(p.getCredentialShareCount, 1<<15, callflag.ReadStates) + p.AddMethod(md, desc) + + // --- Events --- + + eDesc := NewEventDescriptor(AgreementCreatedEvent, + manifest.NewParameter("agreementID", smartcontract.IntegerType), + manifest.NewParameter("remoteChainID", smartcontract.IntegerType), + manifest.NewParameter("agreementType", smartcontract.IntegerType)) + p.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(AgreementUpdatedEvent, + manifest.NewParameter("agreementID", smartcontract.IntegerType), + manifest.NewParameter("oldStatus", smartcontract.IntegerType), + manifest.NewParameter("newStatus", smartcontract.IntegerType)) + p.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(AgreementTerminatedEvent, + manifest.NewParameter("agreementID", smartcontract.IntegerType), + manifest.NewParameter("remoteChainID", smartcontract.IntegerType)) + p.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(VerificationRequestedEvent, + manifest.NewParameter("requestID", smartcontract.IntegerType), + manifest.NewParameter("targetChainID", smartcontract.IntegerType), + manifest.NewParameter("subject", smartcontract.Hash160Type), + manifest.NewParameter("verificationType", smartcontract.IntegerType)) + p.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(VerificationRespondedEvent, + manifest.NewParameter("requestID", smartcontract.IntegerType), + manifest.NewParameter("approved", smartcontract.BoolType)) + p.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(SettlementRequestedEvent, + manifest.NewParameter("settlementID", smartcontract.IntegerType), + manifest.NewParameter("toChainID", smartcontract.IntegerType), + manifest.NewParameter("receiver", smartcontract.Hash160Type), + manifest.NewParameter("amount", smartcontract.IntegerType)) + p.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(SettlementCompletedEvent, + manifest.NewParameter("settlementID", smartcontract.IntegerType), + manifest.NewParameter("txHash", smartcontract.Hash256Type)) + p.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(CredentialSharedEvent, + manifest.NewParameter("shareID", smartcontract.IntegerType), + manifest.NewParameter("owner", smartcontract.Hash160Type), + manifest.NewParameter("targetChainID", smartcontract.IntegerType), + manifest.NewParameter("credentialType", smartcontract.IntegerType)) + p.AddEvent(NewEvent(eDesc)) + + eDesc = NewEventDescriptor(CredentialRevokedEvent, + manifest.NewParameter("shareID", smartcontract.IntegerType), + manifest.NewParameter("owner", smartcontract.Hash160Type)) + p.AddEvent(NewEvent(eDesc)) + + return p +} + +// Metadata returns contract metadata. +func (p *Pons) Metadata() *interop.ContractMD { + return &p.ContractMD +} + +// Initialize initializes Pons contract at the specified hardfork. +func (p *Pons) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { + if hf != p.ActiveIn() { + return nil + } + + // Initialize default config + cfg := state.PonsConfig{ + LocalChainID: defaultLocalChainID, + VerificationTimeout: defaultVerificationTimeout, + SettlementTimeout: defaultSettlementTimeout, + MaxPendingRequests: defaultMaxPendingRequests, + CredentialShareExpiry: defaultCredentialShareExpiry, + } + p.setConfigInternal(ic.DAO, &cfg) + + return nil +} + +// InitializeCache fills native Pons cache from DAO on node restart. +func (p *Pons) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error { + return nil +} + +// OnPersist implements the Contract interface. +func (p *Pons) OnPersist(ic *interop.Context) error { + return nil +} + +// PostPersist implements the Contract interface. +func (p *Pons) PostPersist(ic *interop.Context) error { + return nil +} + +// ActiveIn returns the hardfork this contract activates in (nil = always active). +func (p *Pons) ActiveIn() *config.Hardfork { + return nil +} + +// ============================================================================ +// Storage Key Helpers +// ============================================================================ + +func makePonsConfigKey() []byte { + return []byte{ponsPrefixConfig} +} + +func makePonsAgreementKey(agreementID uint64) []byte { + key := make([]byte, 9) + key[0] = ponsPrefixAgreement + binary.BigEndian.PutUint64(key[1:], agreementID) + return key +} + +func makePonsAgreementByChainKey(chainID uint32, agreementID uint64) []byte { + key := make([]byte, 13) + key[0] = ponsPrefixAgreementByChain + binary.BigEndian.PutUint32(key[1:], chainID) + binary.BigEndian.PutUint64(key[5:], agreementID) + return key +} + +func makePonsAgreementCounterKey() []byte { + return []byte{ponsPrefixAgreementCounter} +} + +func makePonsVerificationKey(requestID uint64) []byte { + key := make([]byte, 9) + key[0] = ponsPrefixVerification + binary.BigEndian.PutUint64(key[1:], requestID) + return key +} + +func makePonsVerifBySubjectKey(subject util.Uint160, requestID uint64) []byte { + key := make([]byte, 1+util.Uint160Size+8) + key[0] = ponsPrefixVerifBySubject + copy(key[1:], subject.BytesBE()) + binary.BigEndian.PutUint64(key[1+util.Uint160Size:], requestID) + return key +} + +func makePonsVerificationCounterKey() []byte { + return []byte{ponsPrefixVerifCounter} +} + +func makePonsSettlementKey(settlementID uint64) []byte { + key := make([]byte, 9) + key[0] = ponsPrefixSettlement + binary.BigEndian.PutUint64(key[1:], settlementID) + return key +} + +func makePonsSettlByChainKey(chainID uint32, settlementID uint64) []byte { + key := make([]byte, 13) + key[0] = ponsPrefixSettlByChain + binary.BigEndian.PutUint32(key[1:], chainID) + binary.BigEndian.PutUint64(key[5:], settlementID) + return key +} + +func makePonsSettlementCounterKey() []byte { + return []byte{ponsPrefixSettlCounter} +} + +func makePonsCredentialKey(credentialID uint64) []byte { + key := make([]byte, 9) + key[0] = ponsPrefixCredential + binary.BigEndian.PutUint64(key[1:], credentialID) + return key +} + +func makePonsCredByOwnerKey(owner util.Uint160, credentialID uint64) []byte { + key := make([]byte, 1+util.Uint160Size+8) + key[0] = ponsPrefixCredByOwner + copy(key[1:], owner.BytesBE()) + binary.BigEndian.PutUint64(key[1+util.Uint160Size:], credentialID) + return key +} + +func makePonsCredentialCounterKey() []byte { + return []byte{ponsPrefixCredCounter} +} + +// ============================================================================ +// Internal Storage Methods +// ============================================================================ + +func (p *Pons) getConfigInternal(d *dao.Simple) *state.PonsConfig { + si := d.GetStorageItem(p.ID, makePonsConfigKey()) + if si == nil { + return &state.PonsConfig{ + LocalChainID: defaultLocalChainID, + VerificationTimeout: defaultVerificationTimeout, + SettlementTimeout: defaultSettlementTimeout, + MaxPendingRequests: defaultMaxPendingRequests, + CredentialShareExpiry: defaultCredentialShareExpiry, + } + } + // Decode config: chainID(4) + verifTimeout(4) + settlTimeout(4) + maxReq(8) + credExpiry(4) = 24 bytes + if len(si) < 24 { + return &state.PonsConfig{ + LocalChainID: defaultLocalChainID, + VerificationTimeout: defaultVerificationTimeout, + SettlementTimeout: defaultSettlementTimeout, + MaxPendingRequests: defaultMaxPendingRequests, + CredentialShareExpiry: defaultCredentialShareExpiry, + } + } + return &state.PonsConfig{ + LocalChainID: binary.BigEndian.Uint32(si[0:4]), + VerificationTimeout: binary.BigEndian.Uint32(si[4:8]), + SettlementTimeout: binary.BigEndian.Uint32(si[8:12]), + MaxPendingRequests: binary.BigEndian.Uint64(si[12:20]), + CredentialShareExpiry: binary.BigEndian.Uint32(si[20:24]), + } +} + +func (p *Pons) setConfigInternal(d *dao.Simple, cfg *state.PonsConfig) { + buf := make([]byte, 24) + binary.BigEndian.PutUint32(buf[0:4], cfg.LocalChainID) + binary.BigEndian.PutUint32(buf[4:8], cfg.VerificationTimeout) + binary.BigEndian.PutUint32(buf[8:12], cfg.SettlementTimeout) + binary.BigEndian.PutUint64(buf[12:20], cfg.MaxPendingRequests) + binary.BigEndian.PutUint32(buf[20:24], cfg.CredentialShareExpiry) + d.PutStorageItem(p.ID, makePonsConfigKey(), buf) +} + +func (p *Pons) getCounterInternal(d *dao.Simple, key []byte) uint64 { + si := d.GetStorageItem(p.ID, key) + if si == nil || len(si) < 8 { + return 0 + } + return binary.BigEndian.Uint64(si) +} + +func (p *Pons) incrementCounterInternal(d *dao.Simple, key []byte) uint64 { + current := p.getCounterInternal(d, key) + next := current + 1 + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, next) + d.PutStorageItem(p.ID, key, buf) + return next +} + +// Agreement storage format: +// localChainID(4) + remoteChainID(4) + agreementType(1) + status(1) + terms(32) + +// effectiveDate(4) + expirationDate(4) + createdAt(4) + updatedAt(4) = 58 bytes +func (p *Pons) getAgreementInternal(d *dao.Simple, agreementID uint64) (*state.BilateralAgreement, bool) { + si := d.GetStorageItem(p.ID, makePonsAgreementKey(agreementID)) + if si == nil || len(si) < 58 { + return nil, false + } + + var terms util.Uint256 + copy(terms[:], si[10:42]) + + return &state.BilateralAgreement{ + ID: agreementID, + LocalChainID: binary.BigEndian.Uint32(si[0:4]), + RemoteChainID: binary.BigEndian.Uint32(si[4:8]), + AgreementType: state.AgreementType(si[8]), + Status: state.AgreementStatus(si[9]), + Terms: terms, + EffectiveDate: binary.BigEndian.Uint32(si[42:46]), + ExpirationDate: binary.BigEndian.Uint32(si[46:50]), + CreatedAt: binary.BigEndian.Uint32(si[50:54]), + UpdatedAt: binary.BigEndian.Uint32(si[54:58]), + }, true +} + +func (p *Pons) setAgreementInternal(d *dao.Simple, agr *state.BilateralAgreement) { + buf := make([]byte, 58) + binary.BigEndian.PutUint32(buf[0:4], agr.LocalChainID) + binary.BigEndian.PutUint32(buf[4:8], agr.RemoteChainID) + buf[8] = byte(agr.AgreementType) + buf[9] = byte(agr.Status) + copy(buf[10:42], agr.Terms[:]) + binary.BigEndian.PutUint32(buf[42:46], agr.EffectiveDate) + binary.BigEndian.PutUint32(buf[46:50], agr.ExpirationDate) + binary.BigEndian.PutUint32(buf[50:54], agr.CreatedAt) + binary.BigEndian.PutUint32(buf[54:58], agr.UpdatedAt) + d.PutStorageItem(p.ID, makePonsAgreementKey(agr.ID), buf) + // Index by chain + d.PutStorageItem(p.ID, makePonsAgreementByChainKey(agr.RemoteChainID, agr.ID), []byte{1}) +} + +// Verification storage format: +// requestingChain(4) + targetChain(4) + subject(20) + verificationType(1) + dataHash(32) + +// status(1) + responseHash(32) + requester(20) + createdAt(4) + expiresAt(4) + respondedAt(4) = 126 bytes +func (p *Pons) getVerificationInternal(d *dao.Simple, requestID uint64) (*state.VerificationRequest, bool) { + si := d.GetStorageItem(p.ID, makePonsVerificationKey(requestID)) + if si == nil || len(si) < 126 { + return nil, false + } + + var subject util.Uint160 + copy(subject[:], si[8:28]) + var dataHash util.Uint256 + copy(dataHash[:], si[29:61]) + var responseHash util.Uint256 + copy(responseHash[:], si[62:94]) + var requester util.Uint160 + copy(requester[:], si[94:114]) + + return &state.VerificationRequest{ + ID: requestID, + RequestingChain: binary.BigEndian.Uint32(si[0:4]), + TargetChain: binary.BigEndian.Uint32(si[4:8]), + Subject: subject, + VerificationType: state.VerificationType(si[28]), + DataHash: dataHash, + Status: state.VerificationStatus(si[61]), + ResponseHash: responseHash, + Requester: requester, + CreatedAt: binary.BigEndian.Uint32(si[114:118]), + ExpiresAt: binary.BigEndian.Uint32(si[118:122]), + RespondedAt: binary.BigEndian.Uint32(si[122:126]), + }, true +} + +func (p *Pons) setVerificationInternal(d *dao.Simple, vr *state.VerificationRequest) { + buf := make([]byte, 126) + binary.BigEndian.PutUint32(buf[0:4], vr.RequestingChain) + binary.BigEndian.PutUint32(buf[4:8], vr.TargetChain) + copy(buf[8:28], vr.Subject[:]) + buf[28] = byte(vr.VerificationType) + copy(buf[29:61], vr.DataHash[:]) + buf[61] = byte(vr.Status) + copy(buf[62:94], vr.ResponseHash[:]) + copy(buf[94:114], vr.Requester[:]) + binary.BigEndian.PutUint32(buf[114:118], vr.CreatedAt) + binary.BigEndian.PutUint32(buf[118:122], vr.ExpiresAt) + binary.BigEndian.PutUint32(buf[122:126], vr.RespondedAt) + d.PutStorageItem(p.ID, makePonsVerificationKey(vr.ID), buf) + // Index by subject + d.PutStorageItem(p.ID, makePonsVerifBySubjectKey(vr.Subject, vr.ID), []byte{1}) +} + +// Settlement storage format: +// fromChain(4) + toChain(4) + sender(20) + receiver(20) + amount(8) + status(1) + +// createdAt(4) + settledAt(4) + txHash(32) + refLen(2) + reference(var) = 99 + ref bytes +func (p *Pons) getSettlementInternal(d *dao.Simple, settlementID uint64) (*state.SettlementRequest, bool) { + si := d.GetStorageItem(p.ID, makePonsSettlementKey(settlementID)) + if si == nil || len(si) < 99 { + return nil, false + } + + var sender util.Uint160 + copy(sender[:], si[8:28]) + var receiver util.Uint160 + copy(receiver[:], si[28:48]) + var txHash util.Uint256 + copy(txHash[:], si[65:97]) + + refLen := binary.BigEndian.Uint16(si[97:99]) + var reference string + if len(si) >= 99+int(refLen) { + reference = string(si[99 : 99+refLen]) + } + + return &state.SettlementRequest{ + ID: settlementID, + FromChain: binary.BigEndian.Uint32(si[0:4]), + ToChain: binary.BigEndian.Uint32(si[4:8]), + Sender: sender, + Receiver: receiver, + Amount: binary.BigEndian.Uint64(si[48:56]), + Status: state.SettlementStatus(si[56]), + CreatedAt: binary.BigEndian.Uint32(si[57:61]), + SettledAt: binary.BigEndian.Uint32(si[61:65]), + TxHash: txHash, + Reference: reference, + }, true +} + +func (p *Pons) setSettlementInternal(d *dao.Simple, sr *state.SettlementRequest) { + refBytes := []byte(sr.Reference) + buf := make([]byte, 99+len(refBytes)) + binary.BigEndian.PutUint32(buf[0:4], sr.FromChain) + binary.BigEndian.PutUint32(buf[4:8], sr.ToChain) + copy(buf[8:28], sr.Sender[:]) + copy(buf[28:48], sr.Receiver[:]) + binary.BigEndian.PutUint64(buf[48:56], sr.Amount) + buf[56] = byte(sr.Status) + binary.BigEndian.PutUint32(buf[57:61], sr.CreatedAt) + binary.BigEndian.PutUint32(buf[61:65], sr.SettledAt) + copy(buf[65:97], sr.TxHash[:]) + binary.BigEndian.PutUint16(buf[97:99], uint16(len(refBytes))) + copy(buf[99:], refBytes) + d.PutStorageItem(p.ID, makePonsSettlementKey(sr.ID), buf) + // Index by chain + d.PutStorageItem(p.ID, makePonsSettlByChainKey(sr.ToChain, sr.ID), []byte{1}) +} + +// CredentialShare storage format: +// sourceChain(4) + targetChain(4) + owner(20) + credentialType(1) + credentialID(8) + +// contentHash(32) + validUntil(4) + createdAt(4) + isRevoked(1) = 78 bytes +func (p *Pons) getCredentialShareInternal(d *dao.Simple, shareID uint64) (*state.CredentialShare, bool) { + si := d.GetStorageItem(p.ID, makePonsCredentialKey(shareID)) + if si == nil || len(si) < 78 { + return nil, false + } + + var owner util.Uint160 + copy(owner[:], si[8:28]) + var contentHash util.Uint256 + copy(contentHash[:], si[37:69]) + + return &state.CredentialShare{ + ID: shareID, + SourceChain: binary.BigEndian.Uint32(si[0:4]), + TargetChain: binary.BigEndian.Uint32(si[4:8]), + Owner: owner, + CredentialType: state.VerificationType(si[28]), + CredentialID: binary.BigEndian.Uint64(si[29:37]), + ContentHash: contentHash, + ValidUntil: binary.BigEndian.Uint32(si[69:73]), + CreatedAt: binary.BigEndian.Uint32(si[73:77]), + IsRevoked: si[77] != 0, + }, true +} + +func (p *Pons) setCredentialShareInternal(d *dao.Simple, cs *state.CredentialShare) { + buf := make([]byte, 78) + binary.BigEndian.PutUint32(buf[0:4], cs.SourceChain) + binary.BigEndian.PutUint32(buf[4:8], cs.TargetChain) + copy(buf[8:28], cs.Owner[:]) + buf[28] = byte(cs.CredentialType) + binary.BigEndian.PutUint64(buf[29:37], cs.CredentialID) + copy(buf[37:69], cs.ContentHash[:]) + binary.BigEndian.PutUint32(buf[69:73], cs.ValidUntil) + binary.BigEndian.PutUint32(buf[73:77], cs.CreatedAt) + if cs.IsRevoked { + buf[77] = 1 + } + d.PutStorageItem(p.ID, makePonsCredentialKey(cs.ID), buf) + // Index by owner + d.PutStorageItem(p.ID, makePonsCredByOwnerKey(cs.Owner, cs.ID), []byte{1}) +} + +// hasActiveAgreementInternal checks if there's an active agreement with the target chain +// for the specified agreement type. +func (p *Pons) hasActiveAgreementInternal(d *dao.Simple, remoteChainID uint32, agreementType state.AgreementType, blockHeight uint32) bool { + count := p.getCounterInternal(d, makePonsAgreementCounterKey()) + for i := uint64(1); i <= count; i++ { + agr, exists := p.getAgreementInternal(d, i) + if !exists { + continue + } + if agr.RemoteChainID != remoteChainID { + continue + } + if agr.Status != state.AgreementActive { + continue + } + // Check expiration + if agr.ExpirationDate > 0 && agr.ExpirationDate < blockHeight { + continue + } + // Check if agreement type matches or is comprehensive + if agr.AgreementType == agreementType || agr.AgreementType == state.AgreementTypeComprehensive { + return true + } + } + return false +} + +// ============================================================================ +// Contract Methods +// ============================================================================ + +func (p *Pons) getConfig(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + cfg := p.getConfigInternal(ic.DAO) + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(cfg.LocalChainID))), + stackitem.NewBigInteger(big.NewInt(int64(cfg.VerificationTimeout))), + stackitem.NewBigInteger(big.NewInt(int64(cfg.SettlementTimeout))), + stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.MaxPendingRequests)), + stackitem.NewBigInteger(big.NewInt(int64(cfg.CredentialShareExpiry))), + }) +} + +func (p *Pons) setLocalChainID(ic *interop.Context, args []stackitem.Item) stackitem.Item { + chainID := uint32(toBigInt(args[0]).Int64()) + + if !p.NEO.CheckCommittee(ic) { + panic("only committee can set chain ID") + } + + cfg := p.getConfigInternal(ic.DAO) + cfg.LocalChainID = chainID + p.setConfigInternal(ic.DAO, cfg) + + return stackitem.NewBool(true) +} + +func (p *Pons) createAgreement(ic *interop.Context, args []stackitem.Item) stackitem.Item { + remoteChainID := uint32(toBigInt(args[0]).Int64()) + agreementType := state.AgreementType(toBigInt(args[1]).Int64()) + termsHashBytes, err := args[2].TryBytes() + if err != nil { + panic(err) + } + termsHash, err := util.Uint256DecodeBytesBE(termsHashBytes) + if err != nil { + panic(err) + } + expirationHeight := uint32(toBigInt(args[3]).Int64()) + + if !p.NEO.CheckCommittee(ic) { + panic("only committee can create agreements") + } + + if agreementType > state.AgreementTypeComprehensive { + panic(ErrInvalidAgreementType) + } + + cfg := p.getConfigInternal(ic.DAO) + + // Create agreement + agreementID := p.incrementCounterInternal(ic.DAO, makePonsAgreementCounterKey()) + agr := &state.BilateralAgreement{ + ID: agreementID, + LocalChainID: cfg.LocalChainID, + RemoteChainID: remoteChainID, + AgreementType: agreementType, + Status: state.AgreementPending, + Terms: termsHash, + EffectiveDate: 0, // Set when activated + ExpirationDate: expirationHeight, + CreatedAt: ic.Block.Index, + UpdatedAt: ic.Block.Index, + } + p.setAgreementInternal(ic.DAO, agr) + + ic.AddNotification(p.Hash, AgreementCreatedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(agreementID)), + stackitem.NewBigInteger(big.NewInt(int64(remoteChainID))), + stackitem.NewBigInteger(big.NewInt(int64(agreementType))), + })) + + return stackitem.NewBigInteger(new(big.Int).SetUint64(agreementID)) +} + +func (p *Pons) updateAgreementStatus(ic *interop.Context, args []stackitem.Item) stackitem.Item { + agreementID := toBigInt(args[0]).Uint64() + newStatus := state.AgreementStatus(toBigInt(args[1]).Int64()) + + if !p.NEO.CheckCommittee(ic) { + panic("only committee can update agreement status") + } + + agr, exists := p.getAgreementInternal(ic.DAO, agreementID) + if !exists { + panic(ErrAgreementNotFound) + } + + oldStatus := agr.Status + agr.Status = newStatus + agr.UpdatedAt = ic.Block.Index + + // Set effective date when activating + if newStatus == state.AgreementActive && oldStatus != state.AgreementActive { + agr.EffectiveDate = ic.Block.Index + } + + p.setAgreementInternal(ic.DAO, agr) + + ic.AddNotification(p.Hash, AgreementUpdatedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(agreementID)), + stackitem.NewBigInteger(big.NewInt(int64(oldStatus))), + stackitem.NewBigInteger(big.NewInt(int64(newStatus))), + })) + + return stackitem.NewBool(true) +} + +func (p *Pons) getAgreement(ic *interop.Context, args []stackitem.Item) stackitem.Item { + agreementID := toBigInt(args[0]).Uint64() + + agr, exists := p.getAgreementInternal(ic.DAO, agreementID) + if !exists { + return stackitem.Null{} + } + + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(agr.ID)), + stackitem.NewBigInteger(big.NewInt(int64(agr.LocalChainID))), + stackitem.NewBigInteger(big.NewInt(int64(agr.RemoteChainID))), + stackitem.NewBigInteger(big.NewInt(int64(agr.AgreementType))), + stackitem.NewBigInteger(big.NewInt(int64(agr.Status))), + stackitem.NewByteArray(agr.Terms.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(agr.EffectiveDate))), + stackitem.NewBigInteger(big.NewInt(int64(agr.ExpirationDate))), + stackitem.NewBigInteger(big.NewInt(int64(agr.CreatedAt))), + stackitem.NewBigInteger(big.NewInt(int64(agr.UpdatedAt))), + }) +} + +func (p *Pons) hasActiveAgreement(ic *interop.Context, args []stackitem.Item) stackitem.Item { + remoteChainID := uint32(toBigInt(args[0]).Int64()) + agreementType := state.AgreementType(toBigInt(args[1]).Int64()) + + has := p.hasActiveAgreementInternal(ic.DAO, remoteChainID, agreementType, ic.Block.Index) + return stackitem.NewBool(has) +} + +func (p *Pons) requestVerification(ic *interop.Context, args []stackitem.Item) stackitem.Item { + targetChainID := uint32(toBigInt(args[0]).Int64()) + subject := toUint160(args[1]) + verificationType := state.VerificationType(toBigInt(args[2]).Int64()) + dataHashBytes, err := args[3].TryBytes() + if err != nil { + panic(err) + } + dataHash, err := util.Uint256DecodeBytesBE(dataHashBytes) + if err != nil { + panic(err) + } + + cfg := p.getConfigInternal(ic.DAO) + + // Check for active agreement + agreementType := state.AgreementTypeIdentity + switch verificationType { + case state.VerificationTypeCredential, state.VerificationTypeCertificate: + agreementType = state.AgreementTypeEducation + case state.VerificationTypeHealth: + agreementType = state.AgreementTypeHealthcare + } + + if !p.hasActiveAgreementInternal(ic.DAO, targetChainID, agreementType, ic.Block.Index) { + panic(ErrNoAgreement) + } + + // Get requester from caller + requester := ic.VM.GetCallingScriptHash() + + // Create verification request + requestID := p.incrementCounterInternal(ic.DAO, makePonsVerificationCounterKey()) + vr := &state.VerificationRequest{ + ID: requestID, + RequestingChain: cfg.LocalChainID, + TargetChain: targetChainID, + Subject: subject, + VerificationType: verificationType, + DataHash: dataHash, + Status: state.VerificationPending, + Requester: requester, + CreatedAt: ic.Block.Index, + ExpiresAt: ic.Block.Index + cfg.VerificationTimeout, + } + p.setVerificationInternal(ic.DAO, vr) + + ic.AddNotification(p.Hash, VerificationRequestedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(requestID)), + stackitem.NewBigInteger(big.NewInt(int64(targetChainID))), + stackitem.NewByteArray(subject.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(verificationType))), + })) + + return stackitem.NewBigInteger(new(big.Int).SetUint64(requestID)) +} + +func (p *Pons) respondVerification(ic *interop.Context, args []stackitem.Item) stackitem.Item { + requestID := toBigInt(args[0]).Uint64() + approved := toBool(args[1]) + responseHashBytes, err := args[2].TryBytes() + if err != nil { + panic(err) + } + responseHash, err := util.Uint256DecodeBytesBE(responseHashBytes) + if err != nil { + panic(err) + } + + if !p.NEO.CheckCommittee(ic) { + panic("only committee can respond to verification requests") + } + + vr, exists := p.getVerificationInternal(ic.DAO, requestID) + if !exists { + panic(ErrVerificationNotFound) + } + + if vr.Status != state.VerificationPending { + panic("verification request already processed") + } + + if ic.Block.Index > vr.ExpiresAt { + vr.Status = state.VerificationExpired + p.setVerificationInternal(ic.DAO, vr) + panic(ErrVerificationExpired) + } + + if approved { + vr.Status = state.VerificationApproved + } else { + vr.Status = state.VerificationRejected + } + vr.ResponseHash = responseHash + vr.RespondedAt = ic.Block.Index + p.setVerificationInternal(ic.DAO, vr) + + ic.AddNotification(p.Hash, VerificationRespondedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(requestID)), + stackitem.NewBool(approved), + })) + + return stackitem.NewBool(true) +} + +func (p *Pons) getVerificationRequest(ic *interop.Context, args []stackitem.Item) stackitem.Item { + requestID := toBigInt(args[0]).Uint64() + + vr, exists := p.getVerificationInternal(ic.DAO, requestID) + if !exists { + return stackitem.Null{} + } + + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(vr.ID)), + stackitem.NewBigInteger(big.NewInt(int64(vr.RequestingChain))), + stackitem.NewBigInteger(big.NewInt(int64(vr.TargetChain))), + stackitem.NewByteArray(vr.Subject.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(vr.VerificationType))), + stackitem.NewByteArray(vr.DataHash.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(vr.Status))), + stackitem.NewByteArray(vr.ResponseHash.BytesBE()), + stackitem.NewByteArray(vr.Requester.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(vr.CreatedAt))), + stackitem.NewBigInteger(big.NewInt(int64(vr.ExpiresAt))), + stackitem.NewBigInteger(big.NewInt(int64(vr.RespondedAt))), + }) +} + +func (p *Pons) requestSettlement(ic *interop.Context, args []stackitem.Item) stackitem.Item { + toChainID := uint32(toBigInt(args[0]).Int64()) + receiver := toUint160(args[1]) + amount := toBigInt(args[2]).Uint64() + reference := toString(args[3]) + + cfg := p.getConfigInternal(ic.DAO) + + // Check for settlement agreement + if !p.hasActiveAgreementInternal(ic.DAO, toChainID, state.AgreementTypeSettlement, ic.Block.Index) { + panic(ErrNoAgreement) + } + + sender := ic.VM.GetCallingScriptHash() + + // Create settlement request + settlementID := p.incrementCounterInternal(ic.DAO, makePonsSettlementCounterKey()) + sr := &state.SettlementRequest{ + ID: settlementID, + FromChain: cfg.LocalChainID, + ToChain: toChainID, + Sender: sender, + Receiver: receiver, + Amount: amount, + Status: state.SettlementPending, + Reference: reference, + CreatedAt: ic.Block.Index, + } + p.setSettlementInternal(ic.DAO, sr) + + ic.AddNotification(p.Hash, SettlementRequestedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(settlementID)), + stackitem.NewBigInteger(big.NewInt(int64(toChainID))), + stackitem.NewByteArray(receiver.BytesBE()), + stackitem.NewBigInteger(new(big.Int).SetUint64(amount)), + })) + + return stackitem.NewBigInteger(new(big.Int).SetUint64(settlementID)) +} + +func (p *Pons) completeSettlement(ic *interop.Context, args []stackitem.Item) stackitem.Item { + settlementID := toBigInt(args[0]).Uint64() + txHashBytes, err := args[1].TryBytes() + if err != nil { + panic(err) + } + txHash, err := util.Uint256DecodeBytesBE(txHashBytes) + if err != nil { + panic(err) + } + + if !p.NEO.CheckCommittee(ic) { + panic("only committee can complete settlements") + } + + sr, exists := p.getSettlementInternal(ic.DAO, settlementID) + if !exists { + panic(ErrSettlementNotFound) + } + + if sr.Status != state.SettlementPending { + panic("settlement already processed") + } + + sr.Status = state.SettlementCompleted + sr.SettledAt = ic.Block.Index + sr.TxHash = txHash + p.setSettlementInternal(ic.DAO, sr) + + ic.AddNotification(p.Hash, SettlementCompletedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(settlementID)), + stackitem.NewByteArray(txHash.BytesBE()), + })) + + return stackitem.NewBool(true) +} + +func (p *Pons) cancelSettlement(ic *interop.Context, args []stackitem.Item) stackitem.Item { + settlementID := toBigInt(args[0]).Uint64() + + sr, exists := p.getSettlementInternal(ic.DAO, settlementID) + if !exists { + panic(ErrSettlementNotFound) + } + + if sr.Status != state.SettlementPending { + panic("settlement already processed") + } + + // Allow sender or committee to cancel + caller := ic.VM.GetCallingScriptHash() + if !caller.Equals(sr.Sender) && !p.NEO.CheckCommittee(ic) { + panic("only sender or committee can cancel settlement") + } + + sr.Status = state.SettlementCancelled + p.setSettlementInternal(ic.DAO, sr) + + return stackitem.NewBool(true) +} + +func (p *Pons) getSettlementRequest(ic *interop.Context, args []stackitem.Item) stackitem.Item { + settlementID := toBigInt(args[0]).Uint64() + + sr, exists := p.getSettlementInternal(ic.DAO, settlementID) + if !exists { + return stackitem.Null{} + } + + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(sr.ID)), + stackitem.NewBigInteger(big.NewInt(int64(sr.FromChain))), + stackitem.NewBigInteger(big.NewInt(int64(sr.ToChain))), + stackitem.NewByteArray(sr.Sender.BytesBE()), + stackitem.NewByteArray(sr.Receiver.BytesBE()), + stackitem.NewBigInteger(new(big.Int).SetUint64(sr.Amount)), + stackitem.NewBigInteger(big.NewInt(int64(sr.Status))), + stackitem.NewByteArray(sr.TxHash.BytesBE()), + stackitem.NewByteArray([]byte(sr.Reference)), + stackitem.NewBigInteger(big.NewInt(int64(sr.CreatedAt))), + stackitem.NewBigInteger(big.NewInt(int64(sr.SettledAt))), + }) +} + +func (p *Pons) shareCredential(ic *interop.Context, args []stackitem.Item) stackitem.Item { + targetChainID := uint32(toBigInt(args[0]).Int64()) + credentialType := state.VerificationType(toBigInt(args[1]).Int64()) + credentialID := toBigInt(args[2]).Uint64() + contentHashBytes, err := args[3].TryBytes() + if err != nil { + panic(err) + } + contentHash, err := util.Uint256DecodeBytesBE(contentHashBytes) + if err != nil { + panic(err) + } + validUntil := uint32(toBigInt(args[4]).Int64()) + + cfg := p.getConfigInternal(ic.DAO) + + // Determine agreement type needed + agreementType := state.AgreementTypeEducation + if credentialType == state.VerificationTypeHealth { + agreementType = state.AgreementTypeHealthcare + } + + if !p.hasActiveAgreementInternal(ic.DAO, targetChainID, agreementType, ic.Block.Index) { + panic(ErrNoAgreement) + } + + owner := ic.VM.GetCallingScriptHash() + + // Set default validity if not provided + if validUntil == 0 { + validUntil = ic.Block.Index + cfg.CredentialShareExpiry + } + + // Create credential share + shareID := p.incrementCounterInternal(ic.DAO, makePonsCredentialCounterKey()) + cs := &state.CredentialShare{ + ID: shareID, + SourceChain: cfg.LocalChainID, + TargetChain: targetChainID, + Owner: owner, + CredentialType: credentialType, + CredentialID: credentialID, + ContentHash: contentHash, + ValidUntil: validUntil, + CreatedAt: ic.Block.Index, + IsRevoked: false, + } + p.setCredentialShareInternal(ic.DAO, cs) + + ic.AddNotification(p.Hash, CredentialSharedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(shareID)), + stackitem.NewByteArray(owner.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(targetChainID))), + stackitem.NewBigInteger(big.NewInt(int64(credentialType))), + })) + + return stackitem.NewBigInteger(new(big.Int).SetUint64(shareID)) +} + +func (p *Pons) revokeCredentialShare(ic *interop.Context, args []stackitem.Item) stackitem.Item { + shareID := toBigInt(args[0]).Uint64() + + cs, exists := p.getCredentialShareInternal(ic.DAO, shareID) + if !exists { + panic(ErrCredentialNotFound) + } + + // Allow owner or committee to revoke + caller := ic.VM.GetCallingScriptHash() + if !caller.Equals(cs.Owner) && !p.NEO.CheckCommittee(ic) { + panic(ErrNotCredentialOwner) + } + + cs.IsRevoked = true + p.setCredentialShareInternal(ic.DAO, cs) + + ic.AddNotification(p.Hash, CredentialRevokedEvent, stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(shareID)), + stackitem.NewByteArray(cs.Owner.BytesBE()), + })) + + return stackitem.NewBool(true) +} + +func (p *Pons) getCredentialShare(ic *interop.Context, args []stackitem.Item) stackitem.Item { + shareID := toBigInt(args[0]).Uint64() + + cs, exists := p.getCredentialShareInternal(ic.DAO, shareID) + if !exists { + return stackitem.Null{} + } + + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(new(big.Int).SetUint64(cs.ID)), + stackitem.NewBigInteger(big.NewInt(int64(cs.SourceChain))), + stackitem.NewBigInteger(big.NewInt(int64(cs.TargetChain))), + stackitem.NewByteArray(cs.Owner.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(cs.CredentialType))), + stackitem.NewBigInteger(new(big.Int).SetUint64(cs.CredentialID)), + stackitem.NewByteArray(cs.ContentHash.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(cs.ValidUntil))), + stackitem.NewBigInteger(big.NewInt(int64(cs.CreatedAt))), + stackitem.NewBool(cs.IsRevoked), + }) +} + +func (p *Pons) verifyCredentialShare(ic *interop.Context, args []stackitem.Item) stackitem.Item { + shareID := toBigInt(args[0]).Uint64() + + cs, exists := p.getCredentialShareInternal(ic.DAO, shareID) + if !exists { + return stackitem.NewBool(false) + } + + if cs.IsRevoked { + return stackitem.NewBool(false) + } + + if ic.Block.Index > cs.ValidUntil { + return stackitem.NewBool(false) + } + + return stackitem.NewBool(true) +} + +func (p *Pons) getAgreementCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + count := p.getCounterInternal(ic.DAO, makePonsAgreementCounterKey()) + return stackitem.NewBigInteger(new(big.Int).SetUint64(count)) +} + +func (p *Pons) getVerificationCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + count := p.getCounterInternal(ic.DAO, makePonsVerificationCounterKey()) + return stackitem.NewBigInteger(new(big.Int).SetUint64(count)) +} + +func (p *Pons) getSettlementCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + count := p.getCounterInternal(ic.DAO, makePonsSettlementCounterKey()) + return stackitem.NewBigInteger(new(big.Int).SetUint64(count)) +} + +func (p *Pons) getCredentialShareCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + count := p.getCounterInternal(ic.DAO, makePonsCredentialCounterKey()) + return stackitem.NewBigInteger(new(big.Int).SetUint64(count)) +} diff --git a/pkg/core/state/pons.go b/pkg/core/state/pons.go new file mode 100644 index 0000000..17a8c18 --- /dev/null +++ b/pkg/core/state/pons.go @@ -0,0 +1,130 @@ +package state + +import ( + "github.com/tutus-one/tutus-chain/pkg/util" +) + +// AgreementStatus represents the status of a bilateral agreement. +type AgreementStatus uint8 + +// Agreement status constants. +const ( + AgreementPending AgreementStatus = 0 + AgreementActive AgreementStatus = 1 + AgreementSuspended AgreementStatus = 2 + AgreementTerminated AgreementStatus = 3 +) + +// AgreementType represents the type of bilateral agreement. +type AgreementType uint8 + +// Agreement type constants. +const ( + AgreementTypeGeneral AgreementType = 0 // General cooperation + AgreementTypeIdentity AgreementType = 1 // Identity verification + AgreementTypeSettlement AgreementType = 2 // VTS settlement + AgreementTypeEducation AgreementType = 3 // Education credential sharing + AgreementTypeHealthcare AgreementType = 4 // Healthcare record sharing + AgreementTypeComprehensive AgreementType = 5 // All services +) + +// VerificationStatus represents the status of a verification request. +type VerificationStatus uint8 + +// Verification status constants. +const ( + VerificationPending VerificationStatus = 0 + VerificationApproved VerificationStatus = 1 + VerificationRejected VerificationStatus = 2 + VerificationExpired VerificationStatus = 3 +) + +// VerificationType represents the type of verification request. +type VerificationType uint8 + +// Verification type constants. +const ( + VerificationTypeIdentity VerificationType = 0 // Identity verification + VerificationTypeCredential VerificationType = 1 // Education credential + VerificationTypeHealth VerificationType = 2 // Healthcare record + VerificationTypeCertificate VerificationType = 3 // Professional certificate +) + +// SettlementStatus represents the status of a settlement request. +type SettlementStatus uint8 + +// Settlement status constants. +const ( + SettlementPending SettlementStatus = 0 + SettlementCompleted SettlementStatus = 1 + SettlementRejected SettlementStatus = 2 + SettlementCancelled SettlementStatus = 3 +) + +// BilateralAgreement represents an agreement between two sovereign chains. +type BilateralAgreement struct { + ID uint64 // Unique agreement ID + LocalChainID uint32 // This chain's ID + RemoteChainID uint32 // Partner chain's ID + AgreementType AgreementType // Type of agreement + Status AgreementStatus + Terms util.Uint256 // Hash of off-chain terms document + EffectiveDate uint32 // Block height when effective + ExpirationDate uint32 // Block height when expires (0 = no expiry) + CreatedAt uint32 // Block height when created + UpdatedAt uint32 // Last update block height +} + +// VerificationRequest represents a cross-border verification request. +type VerificationRequest struct { + ID uint64 // Unique request ID + RequestingChain uint32 // Chain requesting verification + TargetChain uint32 // Chain being queried + Subject util.Uint160 // Subject of verification + VerificationType VerificationType + DataHash util.Uint256 // Hash of requested data + Status VerificationStatus + ResponseHash util.Uint256 // Hash of response data (if any) + Requester util.Uint160 // Who initiated request + CreatedAt uint32 // Block height + ExpiresAt uint32 // Request expiry + RespondedAt uint32 // When responded (0 = pending) +} + +// SettlementRequest represents an international VTS settlement request. +type SettlementRequest struct { + ID uint64 // Unique request ID + FromChain uint32 // Originating chain + ToChain uint32 // Destination chain + Sender util.Uint160 // Sender on from chain + Receiver util.Uint160 // Receiver on to chain + Amount uint64 // VTS amount (in smallest units) + Reference string // Payment reference + Status SettlementStatus + CreatedAt uint32 // Block height + SettledAt uint32 // When settled (0 = pending) + TxHash util.Uint256 // Settlement transaction hash +} + +// CredentialShare represents a shared credential between chains. +type CredentialShare struct { + ID uint64 // Unique share ID + SourceChain uint32 // Chain where credential originated + TargetChain uint32 // Chain receiving credential + Owner util.Uint160 // Credential owner + CredentialType VerificationType + CredentialID uint64 // Original credential ID on source chain + ContentHash util.Uint256 // Hash of credential content + ValidUntil uint32 // Validity period on target chain + CreatedAt uint32 // Block height + IsRevoked bool // Has been revoked +} + +// PonsConfig represents configurable parameters for the Pons contract. +type PonsConfig struct { + LocalChainID uint32 // This chain's unique identifier + VerificationTimeout uint32 // Blocks until verification request expires + SettlementTimeout uint32 // Blocks until settlement request expires + MaxPendingRequests uint64 // Maximum pending requests per chain + CredentialShareExpiry uint32 // Default validity period for shared credentials +}