Sistem Kontrak Cerdas Anti-Korupsi memakai dua kontrak Solidity untuk mengelola transfer dana, menerapkan persetujuan petugas kepatuhan untuk transaksi di atas ambang batas, serta mengintegrasikan data off-chain demi transparansi dan akuntabilitas
Kita akan mengembangkan dua kontrak pintar Solidity yang diberi nama CompanyContract
dan ComplianceOfficerContract
. Kontrak-kontrak ini akan bekerja sama untuk mengelola transfer dana dalam suatu perusahaan melalui alur kerja persetujuan berbasis kepatuhan. Kontrak-kontrak ini akan memberlakukan aturan transaksi, mengidentifikasi dan menangani transaksi yang mencurigakan, dan memungkinkan petugas kepatuhan yang ditunjuk untuk meninjau dan menyetujui atau menolaknya.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title CompanyContract
* @dev Manages company funds, defines transfer approval rules, and interacts with the ComplianceOfficerContract.
* This contract simulates interaction with a bank account by holding funds.
*/
contract CompanyContract {
address public owner; // The deployer of the contract, can set initial configurations.
address public complianceOfficerContractAddress; // Address of the deployed ComplianceOfficerContract.
uint256 public transferThreshold; // The amount (in smallest unit, e.g., wei) above which a transfer is considered suspicious.
// Represents the status of a transfer within the CompanyContract.
enum TransferStatus {
PendingApproval, // Waiting for approval from ComplianceOfficerContract.
Approved, // Approved by ComplianceOfficerContract, ready for execution.
Rejected, // Rejected by ComplianceOfficerContract.
Executed, // Transfer has been successfully sent.
Failed // Transfer failed during execution.
}
// Struct to store details of a transfer initiated by an employee.
struct CompanyTransfer {
address payable recipient; // The address to which funds are to be sent.
uint256 amount; // The amount of the transfer.
address initiator; // The employee who initiated the transfer.
TransferStatus status; // Current status of the transfer.
uint256 timestamp; // Timestamp when the transfer was initiated.
}
// Mapping to store all transfers, indexed by a unique transfer ID.
mapping(uint256 => CompanyTransfer) public companyTransfers;
uint256 private nextTransferId; // Counter for unique transfer IDs.
// Events for transparency and off-chain monitoring.
event TransferInitiated(uint256 indexed transferId, address indexed initiator, address recipient, uint256 amount, bool isSuspicious);
event TransferApprovedByCompliance(uint256 indexed transferId, address indexed recipient, uint256 amount);
event TransferRejectedByCompliance(uint256 indexed transferId, address indexed recipient, uint256 amount);
event TransferExecuted(uint256 indexed transferId, address indexed recipient, uint256 amount);
event TransferFailed(uint256 indexed transferId, address indexed recipient, uint256 amount, string reason);
event ComplianceOfficerContractSet(address indexed _complianceOfficerContractAddress);
event TransferThresholdSet(uint256 _newThreshold);
/**
* @dev Constructor to set the owner and initial transfer threshold.
* @param _initialThreshold The initial threshold for suspicious transactions (e.g., 1,000,000 for Rp 1,000,000).
*/
constructor(uint256 _initialThreshold) {
owner = msg.sender;
transferThreshold = _initialThreshold;
nextTransferId = 1; // Start transfer IDs from 1.
}
/**
* @dev Modifier to restrict access to the contract owner.
*/
modifier onlyOwner() {
require(msg.sender == owner, "CompanyContract: Only owner can call this function.");
_;
}
/**
* @dev Modifier to restrict access to the designated ComplianceOfficerContract.
*/
modifier onlyComplianceOfficerContract() {
require(msg.sender == complianceOfficerContractAddress, "CompanyContract: Only ComplianceOfficerContract can call this function.");
_;
}
/**
* @dev Sets the address of the ComplianceOfficerContract.
* This must be called after both contracts are deployed.
* @param _complianceOfficerContractAddress The address of the deployed ComplianceOfficerContract.
*/
function setComplianceOfficerContract(address _complianceOfficerContractAddress) public onlyOwner {
require(_complianceOfficerContractAddress != address(0), "CompanyContract: Invalid ComplianceOfficerContract address.");
complianceOfficerContractAddress = _complianceOfficerContractAddress;
emit ComplianceOfficerContractSet(_complianceOfficerContractAddress);
}
/**
* @dev Sets a new transfer threshold.
* @param _newThreshold The new threshold amount.
*/
function setTransferThreshold(uint256 _newThreshold) public onlyOwner {
transferThreshold = _newThreshold;
emit TransferThresholdSet(_newThreshold);
}
/**
* @dev Allows the owner to deposit funds into this contract, simulating a company bank account.
* These funds will be used for transfers.
*/
function depositFunds() public payable onlyOwner {
// Funds are automatically added to the contract's balance.
}
/**
* @dev Initiates a fund transfer.
* If the amount exceeds the threshold, it requests approval from the ComplianceOfficerContract.
* Otherwise, it attempts to execute the transfer directly.
* @param _recipient The address to send funds to.
* @param _amount The amount of funds to transfer.
*/
function initiateTransfer(address payable _recipient, uint256 _amount) public {
require(_recipient != address(0), "CompanyContract: Invalid recipient address.");
require(_amount > 0, "CompanyContract: Amount must be greater than zero.");
require(address(this).balance >= _amount, "CompanyContract: Insufficient contract balance for transfer.");
require(complianceOfficerContractAddress != address(0), "CompanyContract: Compliance Officer Contract not set.");
uint256 currentTransferId = nextTransferId++;
// Store the transfer details.
companyTransfers[currentTransferId] = CompanyTransfer({
recipient: _recipient,
amount: _amount,
initiator: msg.sender,
status: TransferStatus.PendingApproval, // Default to pending, even for non-suspicious, then update.
timestamp: block.timestamp
});
if (_amount > transferThreshold) {
// Mark as pending approval and notify ComplianceOfficerContract.
companyTransfers[currentTransferId].status = TransferStatus.PendingApproval;
// Call the ComplianceOfficerContract to request approval.
// This assumes ComplianceOfficerContract has a function `requestApproval(uint256 _companyTransferId, address _recipient, uint256 _amount, address _initiator)`
ComplianceOfficerContract(complianceOfficerContractAddress).requestApproval(
currentTransferId,
_recipient,
_amount,
msg.sender
);
emit TransferInitiated(currentTransferId, msg.sender, _recipient, _amount, true);
} else {
// Directly execute the transfer if it's below the threshold.
companyTransfers[currentTransferId].status = TransferStatus.Approved; // Mark as approved for direct execution.
_executeTransfer(currentTransferId); // Attempt to execute directly.
emit TransferInitiated(currentTransferId, msg.sender, _recipient, _amount, false);
}
}
/**
* @dev Internal function to execute a transfer.
* This function is called by `initiateTransfer` for non-suspicious transactions
* or by `executeApprovedTransfer` for approved suspicious transactions.
* @param _transferId The ID of the transfer to execute.
*/
function _executeTransfer(uint256 _transferId) internal {
CompanyTransfer storage transferToExecute = companyTransfers[_transferId];
require(transferToExecute.status == TransferStatus.Approved, "CompanyContract: Transfer not in Approved status.");
require(address(this).balance >= transferToExecute.amount, "CompanyContract: Insufficient contract balance for execution.");
// Attempt the transfer.
(bool success, ) = transferToExecute.recipient.call{value: transferToExecute.amount}("");
if (success) {
transferToExecute.status = TransferStatus.Executed;
emit TransferExecuted(_transferId, transferToExecute.recipient, transferToExecute.amount);
} else {
transferToExecute.status = TransferStatus.Failed;
emit TransferFailed(_transferId, transferToExecute.recipient, transferToExecute.amount, "Direct transfer failed.");
}
}
/**
* @dev Called by the ComplianceOfficerContract when a suspicious transaction is approved.
* This function then executes the transfer.
* @param _transferId The ID of the transfer that was approved.
*/
function executeApprovedTransfer(uint256 _transferId) public onlyComplianceOfficerContract {
CompanyTransfer storage transferToExecute = companyTransfers[_transferId];
require(transferToExecute.status == TransferStatus.PendingApproval, "CompanyContract: Transfer not pending approval.");
transferToExecute.status = TransferStatus.Approved; // Mark as approved.
emit TransferApprovedByCompliance(_transferId, transferToExecute.recipient, transferToExecute.amount);
_executeTransfer(_transferId); // Proceed with execution.
}
/**
* @dev Called by the ComplianceOfficerContract when a suspicious transaction is rejected.
* @param _transferId The ID of the transfer that was rejected.
*/
function rejectSuspiciousTransfer(uint256 _transferId) public onlyComplianceOfficerContract {
CompanyTransfer storage transferToReject = companyTransfers[_transferId];
require(transferToReject.status == TransferStatus.PendingApproval, "CompanyContract: Transfer not pending approval.");
transferToReject.status = TransferStatus.Rejected; // Mark as rejected.
emit TransferRejectedByCompliance(_transferId, transferToReject.recipient, transferToReject.amount);
}
/**
* @dev Allows the owner to withdraw funds from the contract.
* Useful for managing the contract's balance or in case of emergency.
* @param _amount The amount to withdraw.
*/
function withdrawFunds(uint256 _amount) public onlyOwner {
require(address(this).balance >= _amount, "CompanyContract: Insufficient balance to withdraw.");
payable(owner).transfer(_amount);
}
/**
* @dev Fallback function to receive Ether.
* Allows direct sending of Ether to the contract.
*/
receive() external payable {
// Funds received.
}
}
/**
* @title ComplianceOfficerContract
* @dev Governs the approval workflow for suspicious transactions.
* Allows a designated compliance officer to review and approve/reject transactions.
*/
contract ComplianceOfficerContract {
address public owner; // The deployer of the contract, can set initial configurations.
address public companyContractAddress; // Address of the deployed CompanyContract.
address public designatedComplianceOfficer; // The address of the designated compliance officer.
// Represents the status of a suspicious transaction within this contract.
enum ApprovalStatus {
Pending, // Waiting for review by the compliance officer.
Approved, // Approved by the compliance officer.
Rejected // Rejected by the compliance officer.
}
// Struct to store details of a suspicious transaction awaiting approval.
struct SuspiciousTransaction {
uint256 companyTransferId; // The ID of the corresponding transfer in CompanyContract.
address recipient; // The recipient of the transfer.
uint256 amount; // The amount of the transfer.
address initiator; // The employee who initiated the transfer.
ApprovalStatus status; // Current approval status.
uint256 requestedTimestamp; // Timestamp when approval was requested.
uint256 reviewedTimestamp; // Timestamp when the transaction was reviewed.
bytes32 offChainInfoHash; // Hash of off-chain documents (e.g., invoice IDs, approval documents).
}
// Mapping to store suspicious transactions, indexed by their companyTransferId.
mapping(uint256 => SuspiciousTransaction) public suspiciousTransactions;
// Events for transparency and off-chain monitoring.
event ApprovalRequested(uint256 indexed companyTransferId, address indexed initiator, address recipient, uint256 amount);
event TransactionApproved(uint256 indexed companyTransferId, address indexed approver, bytes32 offChainInfoHash);
event TransactionRejected(uint256 indexed companyTransferId, address indexed rejecter, bytes32 offChainInfoHash);
event CompanyContractSet(address indexed _companyContractAddress);
event ComplianceOfficerSet(address indexed _newOfficer);
/**
* @dev Constructor to set the owner and initial compliance officer.
* @param _initialComplianceOfficer The address of the initial compliance officer.
*/
constructor(address _initialComplianceOfficer) {
owner = msg.sender;
setComplianceOfficer(_initialComplianceOfficer); // Use the setter for initial assignment.
}
/**
* @dev Modifier to restrict access to the contract owner.
*/
modifier onlyOwner() {
require(msg.sender == owner, "ComplianceOfficerContract: Only owner can call this function.");
_;
}
/**
* @dev Modifier to restrict access to the designated compliance officer.
*/
modifier onlyComplianceOfficer() {
require(msg.sender == designatedComplianceOfficer, "ComplianceOfficerContract: Only designated compliance officer can call this function.");
_;
}
/**
* @dev Modifier to restrict access to the designated CompanyContract.
*/
modifier onlyCompanyContract() {
require(msg.sender == companyContractAddress, "ComplianceOfficerContract: Only CompanyContract can call this function.");
_;
}
/**
* @dev Sets the address of the CompanyContract.
* This must be called after both contracts are deployed.
* @param _companyContractAddress The address of the deployed CompanyContract.
*/
function setCompanyContract(address _companyContractAddress) public onlyOwner {
require(_companyContractAddress != address(0), "ComplianceOfficerContract: Invalid CompanyContract address.");
companyContractAddress = _companyContractAddress;
emit CompanyContractSet(_companyContractAddress);
}
/**
* @dev Sets a new designated compliance officer.
* @param _newOfficer The address of the new compliance officer.
*/
function setComplianceOfficer(address _newOfficer) public onlyOwner {
require(_newOfficer != address(0), "ComplianceOfficerContract: Invalid officer address.");
designatedComplianceOfficer = _newOfficer;
emit ComplianceOfficerSet(_newOfficer);
}
/**
* @dev Called by the CompanyContract when a transfer exceeds the threshold and requires approval.
* @param _companyTransferId The ID of the transfer in the CompanyContract.
* @param _recipient The recipient of the transfer.
* @param _amount The amount of the transfer.
* @param _initiator The employee who initiated the transfer.
*/
function requestApproval(
uint256 _companyTransferId,
address _recipient,
uint256 _amount,
address _initiator
) public onlyCompanyContract {
require(suspiciousTransactions[_companyTransferId].status == ApprovalStatus.Pending ||
suspiciousTransactions[_companyTransferId].companyTransferId == 0, // Check if it's a new request
"ComplianceOfficerContract: Transfer already processed or pending.");
suspiciousTransactions[_companyTransferId] = SuspiciousTransaction({
companyTransferId: _companyTransferId,
recipient: _recipient,
amount: _amount,
initiator: _initiator,
status: ApprovalStatus.Pending,
requestedTimestamp: block.timestamp,
reviewedTimestamp: 0, // Not reviewed yet.
offChainInfoHash: 0x0 // No off-chain info initially.
});
emit ApprovalRequested(_companyTransferId, _initiator, _recipient, _amount);
}
/**
* @dev Allows the designated compliance officer to approve a suspicious transaction.
* Upon approval, it calls the CompanyContract to execute the transfer.
* @param _companyTransferId The ID of the transfer to approve.
* @param _offChainInfoHash A hash of off-chain documents (e.g., invoices, external approvals)
* to be stored on-chain for auditability.
*/
function approveTransaction(uint256 _companyTransferId, bytes32 _offChainInfoHash) public onlyComplianceOfficer {
SuspiciousTransaction storage txToApprove = suspiciousTransactions[_companyTransferId];
require(txToApprove.companyTransferId != 0, "ComplianceOfficerContract: Transaction does not exist.");
require(txToApprove.status == ApprovalStatus.Pending, "ComplianceOfficerContract: Transaction not pending approval.");
require(companyContractAddress != address(0), "ComplianceOfficerContract: Company Contract not set.");
txToApprove.status = ApprovalStatus.Approved;
txToApprove.reviewedTimestamp = block.timestamp;
txToApprove.offChainInfoHash = _offChainInfoHash;
// Call back to the CompanyContract to execute the transfer.
CompanyContract(companyContractAddress).executeApprovedTransfer(_companyTransferId);
emit TransactionApproved(_companyTransferId, msg.sender, _offChainInfoHash);
}
/**
* @dev Allows the designated compliance officer to reject a suspicious transaction.
* @param _companyTransferId The ID of the transfer to reject.
* @param _offChainInfoHash A hash of off-chain documents (e.g., reasons for rejection)
* to be stored on-chain for auditability.
*/
function rejectTransaction(uint256 _companyTransferId, bytes32 _offChainInfoHash) public onlyComplianceOfficer {
SuspiciousTransaction storage txToReject = suspiciousTransactions[_companyTransferId];
require(txToReject.companyTransferId != 0, "ComplianceOfficerContract: Transaction does not exist.");
require(txToReject.status == ApprovalStatus.Pending, "ComplianceOfficerContract: Transaction not pending approval.");
require(companyContractAddress != address(0), "ComplianceOfficerContract: Company Contract not set.");
txToReject.status = ApprovalStatus.Rejected;
txToReject.reviewedTimestamp = block.timestamp;
txToReject.offChainInfoHash = _offChainInfoHash;
// Call back to the CompanyContract to mark the transfer as rejected.
CompanyContract(companyContractAddress).rejectSuspiciousTransfer(_companyTransferId);
emit TransactionRejected(_companyTransferId, msg.sender, _offChainInfoHash);
}
}
CompanyContract
disebarkan oleh suatu alamat yang menjadi pemilik dengan hak istimewa administratif penuh. Alamat ComplianceOfficerContract
disimpan di dalamnya, yang harus ditetapkan setelah penyebaran. Fitur yang penting adalah transferThreshold
, yang merupakan suatu nilai (misalnya, 1.000.000) yang menentukan apakah suatu transaksi memerlukan persetujuan kepatuhan.
Transfer dilacak menggunakan enum TransferStatus
dengan status seperti PendingApproval
, Approved
, Rejected
, Executed
, dan Failed
. Struktur CompanyTransfer
menangkap detail transfer seperti penerima, jumlah, pemrakarsa, status, dan stempel waktu. Transfer ini disimpan dalam pemetaan companyTransfers
, diindeks oleh transferId
unik, yang ditingkatkan menggunakan nextTransferId
.
Peristiwa dipancarkan untuk tindakan penting termasuk inisiasi transfer, persetujuan atau penolakan kepatuhan, hasil pelaksanaan, dan perubahan konfigurasi seperti pengaturan alamat kontrak kepatuhan atau pembaruan ambang batas. Peristiwa ini penting bagi sistem di luar rantai untuk memantau aktivitas kontrak.
Konstruktor menginisialisasi pemilik dan menetapkan ambang batas awal. Dua pengubah, onlyOwner
dan onlyComplianceOfficerContract
, membatasi fungsi tertentu baik untuk pemilik maupun kontrak kepatuhan. Pemilik dapat menetapkan alamat kontrak kepatuhan dan memperbarui ambang batas transfer. Dana dapat ditambahkan melalui fungsi depositFunds
.
Karyawan memulai transfer menggunakan initiateTransfer
. Jika jumlahnya melebihi ambang batas, statusnya ditetapkan ke PendingApproval
, dan kontrak memanggil requestApproval
pada ComplianceOfficerContract
. Jika tidak, fungsi _executeTransfer
dipanggil secara langsung untuk menyelesaikan transaksi. Fungsi _executeTransfer
memproses pembayaran dan memperbarui statusnya sebagaimana mestinya. Transfer yang disetujui dapat diselesaikan menggunakan executeApprovedTransfer
, dan yang ditolak ditandai menggunakan rejectSuspiciousTransfer
. Kontrak tersebut juga memungkinkan pemilik untuk menarik dana. Fungsi fallback receive()
memungkinkannya untuk menerima transfer Ether secara langsung.
ComplianceOfficerContract
juga dimiliki oleh deployer-nya. Ia menyimpan alamat CompanyContract
, yang harus ditetapkan setelah kedua kontrak di-deploy. Ia juga menunjuk designatedComplianceOfficer
yang merupakan satu-satunya yang diizinkan untuk menyetujui atau menolak transfer yang mencurigakan.
Kontrak ini menggunakan enum ApprovalStatus
(Tertunda, Disetujui, Ditolak) dan menyimpan transfer mencurigakan dalam pemetaan suspiciousTransactions
, yang diindeks oleh companyTransferId
. Setiap struktur SuspiciousTransaction
berisi ID transfer, penerima, jumlah, pemrakarsa, status, stempel waktu, dan kolom penting: offChainInfoHash
.
Peristiwa seperti ApprovalRequested
, TransactionApproved
, TransactionRejected
, dan pembaruan administratif dipancarkan untuk menjaga transparansi. Konstruktor menetapkan pemilik dan petugas kepatuhan awal. Pengubah membatasi akses ke fungsi berdasarkan apakah pemanggil adalah pemilik, petugas kepatuhan, atau kontrak perusahaan terkait.
Fungsi setCompanyContract
dan setComplianceOfficer
memungkinkan pemilik mengonfigurasi kontrak terkait dan alamat petugas. Ketika CompanyContract
mendeteksi transfer bernilai tinggi, ia memanggil requestApproval
untuk mencatat transaksi mencurigakan dan memancarkan peristiwa untuk diproses oleh sistem di luar rantai.
Petugas kepatuhan menggunakan approveTransaction
atau rejectTransaction
untuk bertindak atas transfer yang tertunda. Kedua fungsi tersebut memperbarui status, menyimpan stempel waktu peninjauan dan offChainInfoHash
, lalu memanggil kembali CompanyContract
untuk mengeksekusi atau menolak transfer. Peristiwa dipancarkan demi transparansi.
Fitur penting adalah penggunaan offChainInfoHash
untuk mengintegrasikan dokumentasi di luar rantai. Ketika petugas kepatuhan meninjau dokumen pendukung (seperti faktur), hash kriptografi dari dokumen tersebut dibuat dan diserahkan bersama dengan keputusan.
Metode ini memastikan integritas, karena setiap manipulasi dokumen akan mengubah hash. Metode ini juga mendukung auditabilitas dengan memungkinkan siapa pun untuk memverifikasi kecocokan antara hash di dalam rantai dan dokumen asli. Privasi terjaga, karena dokumen sebenarnya tetap berada di luar rantai.
Secara keseluruhan, sistem ini menawarkan cara yang aman dan akuntabel untuk mengelola transfer dana dalam organisasi, terutama untuk pemerintah atau badan yang diatur yang memerlukan lapisan kepatuhan. Sistem ini menggabungkan otomatisasi dengan pengawasan manusia dan jejak audit yang dapat diverifikasi.
Mpu Gandring ingin memberantas korupsi di Indonesia dengan teknologi blockchain! Anda ingin mendukung?
- Follow akun Mpu.
- Upvote dan resteem postingan Mpu.
- Share di Instagram, Facebook, X/Twitter dll.
- Biar pemerintah mendengar dan menerapkannya.
Upvoted! Thank you for supporting witness @jswit.