EVM Validator Contract
DOBValidator.sol is the on-chain component of the DobValidator platform. It stores certificate hashes and TRUFA scores on EVM chains, providing a permanent, tamper-proof record of asset verification results that anyone can query.
Contract Overview
| Property | Value |
|---|---|
| Contract | DOBValidator.sol |
| Language | Solidity |
| Access Control | OpenZeppelin AccessControl |
| Key Role | VALIDATOR_ROLE |
| Networks | Sepolia, Polygon Amoy, Base Sepolia, Base Mainnet |
Access Control
DOBValidator uses OpenZeppelin's AccessControl for permissioned operations:
bytes32 public constant VALIDATOR_ROLE = keccak256("VALIDATOR_ROLE");
| Role | Permissions |
|---|---|
DEFAULT_ADMIN_ROLE | Grant/revoke roles, administrative functions |
VALIDATOR_ROLE | Add projects, approve projects with scores |
Only addresses with VALIDATOR_ROLE can write certificate data on-chain. This ensures that only authorized DobValidator instances can attest to asset verification.
TrufaScores Struct
The TRUFA scoring data is stored on-chain using a packed struct:
struct TrufaScores {
uint32 technical; // Technical feasibility (0-100)
uint32 regulatory; // Regulatory compliance (0-100)
uint32 financial; // Financial viability (0-100)
uint32 environmental; // Environmental impact (0-100)
uint32 overall; // Composite score (0-100)
}
Each field is a uint32 storing a value from 0 to 100. The struct is tightly packed (5 * 4 bytes = 20 bytes), fitting in a single storage slot for gas efficiency.
Core Functions
addProject
function addProject(bytes32 _hash) external onlyRole(VALIDATOR_ROLE)
Registers a certificate hash on-chain. This is the first step in the two-step attestation process.
Parameters:
_hash-- The SHA-256 hash of the canonical submission data (0x-prefixed, 32 bytes)
Effects:
- Creates an on-chain record for the hash
- Sets the project state to "registered" (not yet approved)
- Emits a
ProjectAddedevent
Reverts if:
- Caller does not have
VALIDATOR_ROLE - Hash has already been registered
setProjectApproved
function setProjectApproved(
bytes32 _hash,
TrufaScores calldata _scores
) external onlyRole(VALIDATOR_ROLE)
Approves a previously registered project and writes TRUFA scores on-chain. This is the second step in the attestation process.
Parameters:
_hash-- The certificate hash (must have been previously registered withaddProject)_scores-- TheTrufaScoresstruct containing all five dimension scores
Effects:
- Stores the TRUFA scores for the project
- Marks the project as approved
- Emits a
ProjectApprovedevent
Reverts if:
- Caller does not have
VALIDATOR_ROLE - Hash has not been previously registered
- Project is already approved
getProject
function getProject(bytes32 _hash) external view returns (
bool exists,
bool approved,
TrufaScores memory scores,
uint256 registeredAt,
uint256 approvedAt
)
Reads the on-chain record for a certificate hash.
Parameters:
_hash-- The certificate hash to look up
Returns:
exists-- Whether the hash has been registeredapproved-- Whether the project has been approved with scoresscores-- The TRUFA scores (all zeros if not approved)registeredAt-- Block timestamp whenaddProjectwas calledapprovedAt-- Block timestamp whensetProjectApprovedwas called (0 if not approved)
Two-Step Attestation
The attestation process is deliberately split into two steps for operational flexibility:
Step 1: Registration
When a submission enters the UNDER_REVIEW state, the validator registers the hash on-chain:
Validator calls addProject(0xa6352db1...)
--> On-chain record created
--> Project state: registered, not approved
This creates a timestamp proving that the hash was known at a specific block height, even before scoring is complete.
Step 2: Approval
When the submission is approved and TRUFA scores are finalized, the validator writes the scores:
Validator calls setProjectApproved(0xa6352db1..., {
technical: 95,
regulatory: 92,
financial: 85,
environmental: 88,
overall: 90
})
--> Scores stored on-chain
--> Project state: approved
Why Two Steps?
- Separation of concerns -- Registration proves the hash existed at a point in time. Approval proves the scores.
- Auditability -- The time gap between registration and approval is visible on-chain, showing how long the review took.
- Flexibility -- A hash can be registered early in the review process, and scoring can happen asynchronously.
Events
event ProjectAdded(bytes32 indexed hash, uint256 timestamp);
event ProjectApproved(bytes32 indexed hash, TrufaScores scores, uint256 timestamp);
Both events are indexed by hash, making it efficient to query the history of a specific certificate.
Deployment Addresses
| Network | Chain ID | Status |
|---|---|---|
| Ethereum Sepolia | 11155111 | Deployed |
| Polygon Amoy | 80002 | Deployed |
| Base Sepolia | 84532 | Deployed |
| Base Mainnet | 8453 | Deployed |
Verification Flow
To verify a certificate on-chain:
- Take the certificate hash from the DobValidator platform
- Call
getProject(hash)on the appropriate network - Check that
existsistrueandapprovedistrue - Read the
scoresstruct to see the TRUFA breakdown - Compare the on-chain scores with the off-chain certificate data
// Example: Verify a certificate using ethers.js v6
const contract = new ethers.Contract(validatorAddress, abi, provider);
const { exists, approved, scores } = await contract.getProject(certificateHash);
if (exists && approved) {
console.log(`Technical: ${scores.technical}`);
console.log(`Regulatory: ${scores.regulatory}`);
console.log(`Financial: ${scores.financial}`);
console.log(`Environmental: ${scores.environmental}`);
console.log(`Overall: ${scores.overall}`);
} else {
console.log("Certificate not verified on-chain");
}
Integration with Token Studio
When a certificate is linked to a distribution pool on Token Studio, the frontend can cross-reference the on-chain record to display a "verified on-chain" indicator. The pool dashboard shows:
- The certificate hash as a clickable link to the block explorer
- The TRUFA scores pulled from the on-chain contract
- The timestamp of on-chain attestation
This provides an independent verification path that does not rely on the DobValidator backend being available.
Gas Costs
| Function | Approximate Gas |
|---|---|
addProject | ~50,000 gas (new storage slot) |
setProjectApproved | ~60,000 gas (write struct to storage) |
getProject | 0 gas (view function) |
The struct packing (5 x uint32 = 20 bytes in one slot) minimizes storage costs. Both write operations fit comfortably within standard gas limits on all supported networks.