Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements

- Add comprehensive database migrations (001-024) for schema evolution
- Enhance API schema with expanded type definitions and resolvers
- Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth
- Implement new services: AI optimization, billing, blockchain, compliance, marketplace
- Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage)
- Update Crossplane provider with enhanced VM management capabilities
- Add comprehensive test suite for API endpoints and services
- Update frontend components with improved GraphQL subscriptions and real-time updates
- Enhance security configurations and headers (CSP, CORS, etc.)
- Update documentation and configuration files
- Add new CI/CD workflows and validation scripts
- Implement design system improvements and UI enhancements
This commit is contained in:
defiQUG
2025-12-12 18:01:35 -08:00
parent e01131efaf
commit 9daf1fd378
968 changed files with 160890 additions and 1092 deletions

44
blockchain/README.md Normal file
View File

@@ -0,0 +1,44 @@
# Sankofa Phoenix Blockchain
Enterprise Ethereum Alliance (EEA) blockchain implementation for Sankofa Phoenix.
## Platform Choice
**Hyperledger Besu** - Selected as the blockchain platform
- Enterprise-grade Ethereum client
- Permissioning and privacy features
- EEA standards compliant
- Active development and support
## Development Toolchain
### Prerequisites
- Java 17+
- Docker
- Node.js 18+ (for development tools)
### Tools
- **Hardhat**: Smart contract development framework
- **Truffle**: Alternative development framework
- **Web3.js/Ethers.js**: Blockchain interaction libraries
- **Besu**: Blockchain client
## Project Structure
```
blockchain/
├── contracts/ # Smart contracts (Solidity)
├── scripts/ # Deployment and utility scripts
├── tests/ # Smart contract tests
├── hardhat.config.js # Hardhat configuration
├── network-config/ # Besu network configuration
└── README.md
```
## Getting Started
1. Install dependencies: `npm install`
2. Compile contracts: `npx hardhat compile`
3. Run tests: `npx hardhat test`
4. Deploy to test network: `npx hardhat deploy --network besu`

View File

@@ -0,0 +1,140 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title Billing
* @dev Smart contract for tracking billing and resource usage costs
*/
contract Billing {
struct UsageRecord {
string resourceId;
string resourceType;
uint256 startTime;
uint256 endTime;
uint256 cost; // Cost in smallest unit (wei-like)
string currency;
address billedTo;
}
struct Bill {
address billedTo;
uint256 periodStart;
uint256 periodEnd;
uint256 totalCost;
string currency;
bool paid;
uint256 paidAt;
}
mapping(string => UsageRecord[]) public resourceUsage;
mapping(address => Bill[]) public bills;
mapping(string => uint256) public resourceCosts;
event UsageRecorded(
string indexed resourceId,
address indexed billedTo,
uint256 cost,
uint256 timestamp
);
event BillGenerated(
address indexed billedTo,
uint256 billId,
uint256 totalCost,
uint256 periodStart,
uint256 periodEnd
);
event BillPaid(
address indexed billedTo,
uint256 billId,
uint256 paidAt
);
/**
* @dev Record resource usage and cost
*/
function recordUsage(
string memory resourceId,
string memory resourceType,
uint256 startTime,
uint256 endTime,
uint256 cost,
string memory currency,
address billedTo
) public returns (bool) {
UsageRecord memory record = UsageRecord({
resourceId: resourceId,
resourceType: resourceType,
startTime: startTime,
endTime: endTime,
cost: cost,
currency: currency,
billedTo: billedTo
});
resourceUsage[resourceId].push(record);
resourceCosts[resourceId] += cost;
emit UsageRecorded(resourceId, billedTo, cost, block.timestamp);
return true;
}
/**
* @dev Generate a bill for a billing period
*/
function generateBill(
address billedTo,
uint256 periodStart,
uint256 periodEnd
) public returns (uint256) {
uint256 totalCost = 0;
// Calculate total cost from all usage records in period
// This is simplified - actual implementation would aggregate all resources
uint256 billId = bills[billedTo].length;
Bill memory bill = Bill({
billedTo: billedTo,
periodStart: periodStart,
periodEnd: periodEnd,
totalCost: totalCost,
currency: "USD",
paid: false,
paidAt: 0
});
bills[billedTo].push(bill);
emit BillGenerated(billedTo, billId, totalCost, periodStart, periodEnd);
return billId;
}
/**
* @dev Mark a bill as paid
*/
function payBill(address billedTo, uint256 billId) public {
require(billId < bills[billedTo].length, "Bill does not exist");
require(!bills[billedTo][billId].paid, "Bill already paid");
bills[billedTo][billId].paid = true;
bills[billedTo][billId].paidAt = block.timestamp;
emit BillPaid(billedTo, billId, block.timestamp);
}
/**
* @dev Get total cost for a resource
*/
function getResourceTotalCost(string memory resourceId) public view returns (uint256) {
return resourceCosts[resourceId];
}
/**
* @dev Get all bills for an address
*/
function getBills(address billedTo) public view returns (Bill[] memory) {
return bills[billedTo];
}
}

View File

@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title Compliance
* @dev Smart contract for tracking compliance and audit requirements
*/
contract Compliance {
enum ComplianceStatus {
COMPLIANT,
NON_COMPLIANT,
PENDING_REVIEW,
EXEMPTED
}
enum ComplianceFramework {
GDPR,
HIPAA,
SOC2,
ISO27001,
CUSTOM
}
struct ComplianceRecord {
string resourceId;
ComplianceFramework framework;
ComplianceStatus status;
string findings;
address reviewedBy;
uint256 reviewedAt;
uint256 createdAt;
}
mapping(string => ComplianceRecord[]) public complianceRecords;
mapping(string => mapping(ComplianceFramework => ComplianceStatus)) public resourceCompliance;
event ComplianceChecked(
string indexed resourceId,
ComplianceFramework framework,
ComplianceStatus status,
uint256 timestamp
);
event ComplianceReviewed(
string indexed resourceId,
ComplianceFramework framework,
ComplianceStatus status,
address indexed reviewedBy,
uint256 timestamp
);
/**
* @dev Record a compliance check
*/
function recordComplianceCheck(
string memory resourceId,
ComplianceFramework framework,
ComplianceStatus status,
string memory findings
) public returns (bool) {
ComplianceRecord memory record = ComplianceRecord({
resourceId: resourceId,
framework: framework,
status: status,
findings: findings,
reviewedBy: address(0),
reviewedAt: 0,
createdAt: block.timestamp
});
complianceRecords[resourceId].push(record);
resourceCompliance[resourceId][framework] = status;
emit ComplianceChecked(resourceId, framework, status, block.timestamp);
return true;
}
/**
* @dev Review and update compliance status
*/
function reviewCompliance(
string memory resourceId,
ComplianceFramework framework,
ComplianceStatus status,
string memory findings
) public {
ComplianceRecord memory record = ComplianceRecord({
resourceId: resourceId,
framework: framework,
status: status,
findings: findings,
reviewedBy: msg.sender,
reviewedAt: block.timestamp,
createdAt: block.timestamp
});
complianceRecords[resourceId].push(record);
resourceCompliance[resourceId][framework] = status;
emit ComplianceReviewed(resourceId, framework, status, msg.sender, block.timestamp);
}
/**
* @dev Get compliance status for a resource and framework
*/
function getComplianceStatus(
string memory resourceId,
ComplianceFramework framework
) public view returns (ComplianceStatus) {
return resourceCompliance[resourceId][framework];
}
/**
* @dev Get all compliance records for a resource
*/
function getComplianceRecords(string memory resourceId)
public
view
returns (ComplianceRecord[] memory)
{
return complianceRecords[resourceId];
}
}

View File

@@ -0,0 +1,129 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title IdentityManagement
* @dev Smart contract for identity and access management on the blockchain
*/
contract IdentityManagement {
enum Role {
ADMIN,
USER,
VIEWER
}
struct Identity {
address accountAddress;
string userId;
string email;
string name;
Role role;
bool active;
uint256 createdAt;
uint256 updatedAt;
}
mapping(address => Identity) public identities;
mapping(string => address) public userIdToAddress;
address[] public identityAddresses;
event IdentityCreated(
address indexed accountAddress,
string indexed userId,
Role role,
uint256 timestamp
);
event IdentityUpdated(
address indexed accountAddress,
Role newRole,
uint256 timestamp
);
event IdentityDeactivated(
address indexed accountAddress,
uint256 timestamp
);
/**
* @dev Create a new identity
*/
function createIdentity(
address accountAddress,
string memory userId,
string memory email,
string memory name,
Role role
) public returns (bool) {
require(identities[accountAddress].accountAddress == address(0), "Identity already exists");
require(userIdToAddress[userId] == address(0), "User ID already exists");
identities[accountAddress] = Identity({
accountAddress: accountAddress,
userId: userId,
email: email,
name: name,
role: role,
active: true,
createdAt: block.timestamp,
updatedAt: block.timestamp
});
userIdToAddress[userId] = accountAddress;
identityAddresses.push(accountAddress);
emit IdentityCreated(accountAddress, userId, role, block.timestamp);
return true;
}
/**
* @dev Update identity role
*/
function updateIdentityRole(address accountAddress, Role newRole) public {
require(identities[accountAddress].accountAddress != address(0), "Identity does not exist");
require(identities[accountAddress].active, "Identity is not active");
identities[accountAddress].role = newRole;
identities[accountAddress].updatedAt = block.timestamp;
emit IdentityUpdated(accountAddress, newRole, block.timestamp);
}
/**
* @dev Deactivate an identity
*/
function deactivateIdentity(address accountAddress) public {
require(identities[accountAddress].accountAddress != address(0), "Identity does not exist");
identities[accountAddress].active = false;
identities[accountAddress].updatedAt = block.timestamp;
emit IdentityDeactivated(accountAddress, block.timestamp);
}
/**
* @dev Get identity by address
*/
function getIdentity(address accountAddress) public view returns (Identity memory) {
require(identities[accountAddress].accountAddress != address(0), "Identity does not exist");
return identities[accountAddress];
}
/**
* @dev Get identity by user ID
*/
function getIdentityByUserId(string memory userId) public view returns (Identity memory) {
address accountAddress = userIdToAddress[userId];
require(accountAddress != address(0), "User ID not found");
return identities[accountAddress];
}
/**
* @dev Check if address has role
*/
function hasRole(address accountAddress, Role role) public view returns (bool) {
Identity memory identity = identities[accountAddress];
return identity.active && identity.role == role;
}
}

View File

@@ -0,0 +1,123 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title ResourceProvisioning
* @dev Smart contract for tracking resource provisioning on the blockchain
*/
contract ResourceProvisioning {
enum ResourceType {
VM,
CONTAINER,
STORAGE,
NETWORK,
SERVICE
}
struct Resource {
string resourceId;
string region;
string datacenter;
ResourceType resourceType;
uint256 provisionedAt;
address provisionedBy;
bool active;
string metadata; // JSON string
}
mapping(string => Resource) public resources;
mapping(string => bool) public resourceExists;
string[] public resourceIds;
event ResourceProvisioned(
string indexed resourceId,
string region,
ResourceType resourceType,
address indexed provisionedBy,
uint256 timestamp
);
event ResourceDeprovisioned(
string indexed resourceId,
address indexed deprovisionedBy,
uint256 timestamp
);
/**
* @dev Provision a new resource
*/
function provisionResource(
string memory resourceId,
string memory region,
string memory datacenter,
ResourceType resourceType,
string memory metadata
) public returns (bool) {
require(bytes(resourceId).length > 0, "Resource ID cannot be empty");
require(!resourceExists[resourceId], "Resource already exists");
resources[resourceId] = Resource({
resourceId: resourceId,
region: region,
datacenter: datacenter,
resourceType: resourceType,
provisionedAt: block.timestamp,
provisionedBy: msg.sender,
active: true,
metadata: metadata
});
resourceExists[resourceId] = true;
resourceIds.push(resourceId);
emit ResourceProvisioned(
resourceId,
region,
resourceType,
msg.sender,
block.timestamp
);
return true;
}
/**
* @dev Deprovision a resource
*/
function deprovisionResource(string memory resourceId) public {
require(resourceExists[resourceId], "Resource does not exist");
require(resources[resourceId].active, "Resource already deprovisioned");
resources[resourceId].active = false;
emit ResourceDeprovisioned(resourceId, msg.sender, block.timestamp);
}
/**
* @dev Get resource information
*/
function getResource(string memory resourceId)
public
view
returns (Resource memory)
{
require(resourceExists[resourceId], "Resource does not exist");
return resources[resourceId];
}
/**
* @dev Get all resource IDs
*/
function getAllResourceIds() public view returns (string[] memory) {
return resourceIds;
}
/**
* @dev Get resource count
*/
function getResourceCount() public view returns (uint256) {
return resourceIds.length;
}
}

View File

@@ -0,0 +1,117 @@
version: '3.8'
services:
# Hyperledger Besu Validator Node 1
besu-validator-1:
image: hyperledger/besu:latest
container_name: besu-validator-1
ports:
- "8545:8545" # JSON-RPC
- "8546:8546" # WebSocket
- "30303:30303" # P2P
volumes:
- besu-validator-1-data:/var/lib/besu
- ./network-config/genesis.json:/config/genesis.json
- ./network-config/validator-1:/config/keys
command:
- --data-path=/var/lib/besu
- --genesis-file=/config/genesis.json
- --rpc-http-enabled=true
- --rpc-http-host=0.0.0.0
- --rpc-http-port=8545
- --rpc-http-api=ETH,NET,WEB3,ADMIN,EEA,PRIV,IBFT
- --rpc-ws-enabled=true
- --rpc-ws-host=0.0.0.0
- --rpc-ws-port=8546
- --rpc-ws-api=ETH,NET,WEB3,ADMIN,EEA,PRIV,IBFT
- --host-allowlist=*
- --p2p-host=0.0.0.0
- --p2p-port=30303
- --min-gas-price=0
- --network-id=2024
- --bootnodes=enode://validator-1@besu-validator-1:30303
networks:
- besu-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8545"]
interval: 10s
timeout: 5s
retries: 5
# Hyperledger Besu Validator Node 2
besu-validator-2:
image: hyperledger/besu:latest
container_name: besu-validator-2
ports:
- "8547:8545"
- "8548:8546"
- "30304:30303"
volumes:
- besu-validator-2-data:/var/lib/besu
- ./network-config/genesis.json:/config/genesis.json
- ./network-config/validator-2:/config/keys
command:
- --data-path=/var/lib/besu
- --genesis-file=/config/genesis.json
- --rpc-http-enabled=true
- --rpc-http-host=0.0.0.0
- --rpc-http-port=8545
- --rpc-http-api=ETH,NET,WEB3,ADMIN,EEA,PRIV,IBFT
- --rpc-ws-enabled=true
- --rpc-ws-host=0.0.0.0
- --rpc-ws-port=8546
- --rpc-ws-api=ETH,NET,WEB3,ADMIN,EEA,PRIV,IBFT
- --host-allowlist=*
- --p2p-host=0.0.0.0
- --p2p-port=30303
- --min-gas-price=0
- --network-id=2024
- --bootnodes=enode://validator-1@besu-validator-1:30303
networks:
- besu-network
depends_on:
- besu-validator-1
# Hyperledger Besu Validator Node 3
besu-validator-3:
image: hyperledger/besu:latest
container_name: besu-validator-3
ports:
- "8549:8545"
- "8550:8546"
- "30305:30303"
volumes:
- besu-validator-3-data:/var/lib/besu
- ./network-config/genesis.json:/config/genesis.json
- ./network-config/validator-3:/config/keys
command:
- --data-path=/var/lib/besu
- --genesis-file=/config/genesis.json
- --rpc-http-enabled=true
- --rpc-http-host=0.0.0.0
- --rpc-http-port=8545
- --rpc-http-api=ETH,NET,WEB3,ADMIN,EEA,PRIV,IBFT
- --rpc-ws-enabled=true
- --rpc-ws-host=0.0.0.0
- --rpc-ws-port=8546
- --rpc-ws-api=ETH,NET,WEB3,ADMIN,EEA,PRIV,IBFT
- --host-allowlist=*
- --p2p-host=0.0.0.0
- --p2p-port=30303
- --min-gas-price=0
- --network-id=2024
- --bootnodes=enode://validator-1@besu-validator-1:30303
networks:
- besu-network
depends_on:
- besu-validator-1
networks:
besu-network:
driver: bridge
volumes:
besu-validator-1-data:
besu-validator-2-data:
besu-validator-3-data:

View File

@@ -0,0 +1,35 @@
version: '3.8'
services:
besu:
image: hyperledger/besu:latest
container_name: sankofa-besu-node
ports:
- "8545:8545" # JSON-RPC
- "8546:8546" # WebSocket
- "30303:30303" # P2P
volumes:
- besu-data:/var/lib/besu
- ./network-config:/config
command:
- --data-path=/var/lib/besu
- --network-id=2024
- --rpc-http-enabled=true
- --rpc-http-host=0.0.0.0
- --rpc-http-port=8545
- --rpc-http-api=ETH,NET,WEB3,ADMIN
- --rpc-ws-enabled=true
- --rpc-ws-host=0.0.0.0
- --rpc-ws-port=8546
- --p2p-port=30303
- --genesis-file=/config/genesis.json
- --logging=INFO
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8545"]
interval: 10s
timeout: 5s
retries: 5
volumes:
besu-data:

View File

@@ -0,0 +1,35 @@
import { HardhatUserConfig } from 'hardhat/config'
import '@nomicfoundation/hardhat-toolbox'
import * as dotenv from 'dotenv'
dotenv.config()
const config: HardhatUserConfig = {
solidity: {
version: '0.8.24',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
localhost: {
url: 'http://localhost:8545',
},
testnet: {
url: process.env.BESU_RPC_URL || 'http://localhost:8545',
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
paths: {
sources: './contracts',
tests: './tests',
cache: './cache',
artifacts: './artifacts',
},
}
export default config

View File

@@ -0,0 +1,22 @@
{
"config": {
"chainId": 2024,
"constantinopleFixBlock": 0,
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"qip714block": 0
},
"nonce": "0x0",
"timestamp": "0x0",
"extraData": "0x",
"gasLimit": "0x1fffffffffffff",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"alloc": {},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

30
blockchain/package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "sankofa-phoenix-blockchain",
"version": "1.0.0",
"private": true,
"scripts": {
"compile": "hardhat compile",
"test": "hardhat test",
"deploy:local": "hardhat run scripts/deploy.ts --network localhost",
"deploy:test": "hardhat run scripts/deploy.ts --network testnet",
"node:start": "docker-compose up -d besu",
"node:stop": "docker-compose down",
"generate:types": "ts-node scripts/generate-types.ts",
"compile:types": "hardhat compile && pnpm generate:types"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"hardhat": "^2.19.0",
"typescript": "^5.4.0",
"ts-node": "^10.9.0",
"typechain": "^8.3.2"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.0",
"ethers": "^6.9.0",
"web3": "^4.3.0"
}
}

View File

@@ -0,0 +1,49 @@
import { ethers } from 'hardhat'
async function main() {
console.log('Deploying Sankofa Phoenix smart contracts...')
// Get deployer account
const [deployer] = await ethers.getSigners()
console.log('Deploying contracts with account:', deployer.address)
console.log('Account balance:', (await ethers.provider.getBalance(deployer.address)).toString())
// Deploy ResourceProvisioning
const ResourceProvisioningFactory = await ethers.getContractFactory('ResourceProvisioning')
const resourceProvisioning = await ResourceProvisioningFactory.deploy()
await resourceProvisioning.waitForDeployment()
console.log('ResourceProvisioning deployed to:', await resourceProvisioning.getAddress())
// Deploy IdentityManagement
const IdentityManagementFactory = await ethers.getContractFactory('IdentityManagement')
const identityManagement = await IdentityManagementFactory.deploy()
await identityManagement.waitForDeployment()
console.log('IdentityManagement deployed to:', await identityManagement.getAddress())
// Deploy Billing
const BillingFactory = await ethers.getContractFactory('Billing')
const billing = await BillingFactory.deploy()
await billing.waitForDeployment()
console.log('Billing deployed to:', await billing.getAddress())
// Deploy Compliance
const ComplianceFactory = await ethers.getContractFactory('Compliance')
const compliance = await ComplianceFactory.deploy()
await compliance.waitForDeployment()
console.log('Compliance deployed to:', await compliance.getAddress())
console.log('\n✅ All contracts deployed successfully!')
console.log('\nContract Addresses:')
console.log(' ResourceProvisioning:', await resourceProvisioning.getAddress())
console.log(' IdentityManagement:', await identityManagement.getAddress())
console.log(' Billing:', await billing.getAddress())
console.log(' Compliance:', await compliance.getAddress())
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})

View File

@@ -0,0 +1,44 @@
/**
* Generate TypeScript types from compiled smart contracts
* Run: pnpm exec ts-node scripts/generate-types.ts
*/
import { execSync } from 'child_process'
import * as fs from 'fs'
import * as path from 'path'
async function generateTypes() {
console.log('Generating TypeScript types from smart contracts...')
try {
// Run typechain to generate types
execSync('npx typechain --target ethers-v6 --out-dir ../api/src/types/contracts artifacts/contracts/**/*.json', {
cwd: path.join(__dirname, '..'),
stdio: 'inherit',
})
console.log('✓ TypeScript types generated successfully')
console.log('Types are available in: api/src/types/contracts/')
// Create index file for easy imports
const typesDir = path.join(__dirname, '../../api/src/types/contracts')
if (fs.existsSync(typesDir)) {
const indexContent = `// Auto-generated contract type exports
// This file is generated by scripts/generate-types.ts
export * from './ResourceProvisioning'
export * from './IdentityManagement'
export * from './Billing'
export * from './Compliance'
`
fs.writeFileSync(path.join(typesDir, 'index.ts'), indexContent)
console.log('✓ Created index.ts for contract types')
}
} catch (error) {
console.error('Failed to generate types:', error)
process.exit(1)
}
}
generateTypes()

View File

@@ -0,0 +1,74 @@
import { expect } from 'chai'
import { ethers } from 'hardhat'
import { ResourceProvisioning } from '../typechain-types'
describe('ResourceProvisioning', function () {
let contract: ResourceProvisioning
let owner: any
let addr1: any
beforeEach(async function () {
;[owner, addr1] = await ethers.getSigners()
const ResourceProvisioningFactory = await ethers.getContractFactory('ResourceProvisioning')
contract = await ResourceProvisioningFactory.deploy()
await contract.waitForDeployment()
})
describe('Resource Provisioning', function () {
it('Should provision a new resource', async function () {
const tx = await contract.provisionResource(
'resource-001',
'us-east-1',
'datacenter-1',
0, // VM
'{"cpu": 4, "memory": 8192}'
)
await expect(tx)
.to.emit(contract, 'ResourceProvisioned')
.withArgs('resource-001', 'us-east-1', 0, owner.address, await ethers.provider.getBlockNumber())
const resource = await contract.getResource('resource-001')
expect(resource.resourceId).to.equal('resource-001')
expect(resource.active).to.equal(true)
})
it('Should not allow provisioning duplicate resource', async function () {
await contract.provisionResource(
'resource-001',
'us-east-1',
'datacenter-1',
0,
'{}'
)
await expect(
contract.provisionResource(
'resource-001',
'us-east-1',
'datacenter-1',
0,
'{}'
)
).to.be.revertedWith('Resource already exists')
})
it('Should deprovision a resource', async function () {
await contract.provisionResource(
'resource-001',
'us-east-1',
'datacenter-1',
0,
'{}'
)
const tx = await contract.deprovisionResource('resource-001')
await expect(tx).to.emit(contract, 'ResourceDeprovisioned')
const resource = await contract.getResource('resource-001')
expect(resource.active).to.equal(false)
})
})
})