ARA Filesystem Contract Standard defines a common abstract contract inherited by AFS smart contracts. This standard allows AFSs to be treated atomically in an object-oriented way on the blockchain by deploying a smart contract instance for each AFS.
This RFC is under active development and consideration.
ARA Filesystem Contract Standard enables AFSs to have a defined, structured, and self-contained presence on the blockchain. This standard provides basic AFS blockchain interactions such as price-setting, random-access-contract storage, and purchasing.
A standard abstract contract allows any AFS to be used everywhere on ARA, provides greater clarity into how AFSs are represented on the blockchain, and enhances ownership tracking. Furthermore, because we expect ARA tokens to be used for payments, unique contracts for each AFS facilitate more discrete approvals, and thus, greater security.
This section outlines the functions and global variables included in the AFS abstract contract.
The rest of the sections that follow make use of abbreviated forms of the following terminology outlined in this section.
- DID - Decentralized Identifier, the ARA identity of this AFS [1]
contract AFSBase {
address public owner_;
ARAToken public token_;
Library public lib_;
string public did_;
bool public listed_;
uint256 public price_;
mapping(bytes32 => uint256) internal rewards_;
mapping(bytes32 => bool) internal purchasers_;
event Commit(string _did, uint8 _file, uint256 _offset, bytes _buffer);
event Unlisted(string _did);
event PriceSet(string _did, uint256 _price);
event RewardDeposited(string _did, uint256 _reward);
event RewardDistributed(string _did, uint256 _distributed, uint256 _returned);
event Purchased(string _purchaser, string _did, bool _download);
modifier onlyBy(address _account)
{
require(
msg.sender == _account,
"Sender not authorized."
);
_;
}
// Storage (random-access-contract)
mapping(uint8 => Buffers) metadata_;
struct Buffers {
mapping (uint256 => bytes) buffers;
uint256[] offsets;
bool invalid;
}
function setPrice(uint256 _price) external;
function depositReward(uint256 _reward) public returns (bool success);
function distributeReward(address[] _address, uint256[] _amounts) external returns (bool success);
function purchase(string _purchaser, bool _download) external returns (bool success);
// Storage methods (random-access-contract)
function write(uint8 _file, uint256 _offset, bytes _buffer, bool _last_write) external returns (bool success);
function read(uint8 _file, uint256 _offset) public view returns (bytes buffer);
function unlist() public returns (bool success);
}
The owner of this AFS. This is the only address that can modify the storage and pricing of this AFS contract.
address public owner_;
The ARAToken contract.
ARAToken public token_;
The ARA Library contract.
Library public lib_;
The ARA DID of this AFS generated when the AFS was created.
string public did_;
Boolean flag indicating whether this AFS can be purchased.
bool public listed_;
The total price (in ARA tokens) of this AFS.
uint256 public price_;
Mapping that stores reward amounts deposited by each purchaser.
mapping(bytes32 => uint256) internal rewards_;
Mapping that stores the purchasers of this AFS.
mapping(bytes32 => bool) internal purchasers_;
The AFS SLEEP files metadata.tree
and metadata.signatures
. 0
maps to the tree
file and 1
maps the signatures
file.
mapping(uint8 => Buffers) metadata_;
Represents either the tree
or signatures
file
struct Buffers {
mapping (uint256 => bytes) buffers;
uint256[] offsets;
bool invalid;
}
Maps buffer offsets to entries in the tree
or signatures
file
mapping (uint256 => bytes) buffers;
An array of all the offsets in the buffers
mapping for the tree
or signatures
file
uint256[] offsets;
Flag representing the validity of this SLEEP file
bool invalid;
Sets the price (in ARA tokens) of this AFS. Only the owner_
may call this function.
function setPrice(uint256 _price) external;
Deposits a reward allocation to be associated with the msg.sender
.
function depositReward(uint256 _reward) public returns (bool success);
Distributes the previously deposited reward by msg.sender
. The amounts provided by _amounts
are sent to the provided _addresses
, respectively. If there is any reward balance remaining following distribution, it is returned to msg.sender
.
function distributeReward(address[] _address, uint256[] _amounts) external returns (bool success);
Transfers #price
ARA tokens from _purchaser
's wallet and adds this did
to _purchaser
's library in the Library contract. Requires _purchaser
to first approve
this AFS contract address in the ARA Token contract. If _download
is true
, any remaining allowance
is deposited as rewards via depositReward
.
function purchase(string _purchaser, bool _download) external returns (bool success);
Writes _buffer
at _offset
in _file
. If _last_write
is true, emits the Commit
event. If _file
has been marked invalid
, this function reverts. Only owner_
may call this function.
function write(uint8 _file, uint256 _offset, bytes _buffer, bool _last_write) external returns (bool success);
Returns the buffer
located at _offset
in _file
if it exists. Otherwise, returns an empty buffer.
function read(uint8 _file, uint256 _offset) public view returns (bytes buffer);
Invalidates the SLEEP files for this AFS by setting both Buffer
structs to invalid
. listed()
should always return false
after this function is called. Only owner_
may call this function.
function unlist() public returns (bool success);
MUST trigger when the final buffer has been written in a commit.
event Commit(string _did, uint8 _file, uint256 _offset, bytes _buffer);
MUST trigger when an AFS is unlisted.
event Unlisted(string _did);
MUST trigger when an AFS price is set.
event PriceSet(string _did, uint256 _price);
MUST trigger when rewards are deposited.
event RewardDeposited(string _did, uint256 _reward);
MUST trigger when rewards are distributed.
event RewardDistributed(string _did, uint256 _distributed, uint256 _returned);
MUST trigger when an AFS is purchased
event Purchased(string _purchaser, string _did, bool _download);
The following is a real world example of what a deployed AFS contract might look like with method implementations.
contract AFS is AFSBase {
constructor(address _lib, address _token, string _did) public {
owner_ = msg.sender;
token_ = ARAToken(_token);
lib_ = Library(_lib);
did_ = _did;
listed_ = true;
price_ = 0;
}
function setPrice(uint256 _price) external onlyBy(owner_) {
price_ = _price;
emit PriceSet(did_, price_);
}
function depositReward(uint256 _reward) public returns (bool success) {
bytes32 hashedAddress = keccak256(abi.encodePacked(msg.sender));
if (purchasers_[hashedAddress]) {
uint256 allowance = token_.allowance(msg.sender, address(this));
if (allowance >= _reward
&& token_.transferFrom(msg.sender, address(this), _reward)) {
rewards_[hashedAddress] += _reward;
assert(rewards_[hashedAddress] <= token_.balanceOf(address(this)));
emit RewardDeposited(did_, _reward);
return true;
}
}
return false;
}
function distributeReward(address[] _addresses, uint256[] _amounts) public returns (bool success) {
bytes32 hashedAddress = keccak256(abi.encodePacked(msg.sender));
require(rewards_[hashedAddress] > 0 && _addresses.length == _amounts.length);
uint256 totalRewards;
for (uint256 i = 0; i < _amounts.length; i++) {
totalRewards += _amounts[i];
}
require(rewards_[hashedAddress] <= token_.balanceOf(address(this))
&& rewards_[hashedAddress] >= totalRewards);
success = true;
for (uint256 j = 0; j < _addresses.length; j++) {
success = success && token_.transferFrom(address(this), _addresses[j], _amounts[j]);
if (success) {
rewards_[hashedAddress] -= _amounts[j];
}
}
uint256 returned = 0;
if (rewards_[hashedAddress] > 0
&& token_.transferFrom(address(this), msg.sender, rewards_[hashedAddress])) {
returned = rewards_[hashedAddress];
rewards_[hashedAddress] = 0;
}
emit RewardDistributed(did_, totalRewards - returned, returned);
return success;
}
function purchase(string _purchaser, bool _download) external returns (bool success) {
uint256 allowance = token_.allowance(msg.sender, address(this));
require (allowance >= price_); // check if purchaser approved purchase
if (token_.transferFrom(msg.sender, owner_, price_)) {
bytes32 hashedAddress = keccak256(abi.encodePacked(msg.sender));
purchasers_[hashedAddress] = true;
lib_.addLibraryItem(_purchaser, did_);
emit Purchased(_purchaser, did_, _download);
if (_download && allowance > price_) {
depositReward(allowance - price_);
}
return true;
} else {
return false;
}
}
// Storage methods (random-access-contract)
function write(uint8 _file, uint256 _offset, bytes _buffer, bool _last_write) external onlyBy(owner_) returns (bool success){
// make sure AFS hasn't been removed
require(!metadata_[_file].invalid);
metadata_[_file].buffers[_offset] = _buffer;
metadata_[_file].offsets.push(_offset);
if (_last_write) {
emit Commit(did_, _file, _offset, _buffer);
}
return true;
}
function read(uint8 _file, uint256 _offset) public view returns (bytes buffer) {
if (metadata_[_file].invalid) {
return ""; // empty bytes
}
return metadata_[_file].buffers[_offset];
}
function unlist() public onlyBy(owner_) returns (bool success) {
metadata_[0].invalid = true;
metadata_[1].invalid = true;
listed_ = false;
emit Unlisted(did_);
return true;
}
}
N/A
- Create functional smart contracts with mappings for each AFS (i.e., separate contracts for
Storage
,Price
,Purchase
,Rewards
)
This AFS Contract Standard should be built into the AFS commit
function, wherein the first ever commit
for an AFS should deploy a smart contract for that AFS. All relevant AFS and DCDN functions, such as destroy
, price
, rewards
, and purchase
should seek to interface with this contract.
N/A
N/A