Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track prepaid amount and withdraw treasury #110

Merged
merged 10 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions contracts/StorageContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ abstract contract StorageContract is DecentralizedKV, ReentrancyGuardTransient {
/// @notice Prepaid timestamp of last mined
uint256 public prepaidLastMineTime;

/// @notice Fund tracker for prepaid
uint256 public accPrepaidAmount;

// TODO: Reserve extra slots (to a total of 50?) in the storage layout for future upgrades

/// @notice Emitted when a block is mined.
Expand Down Expand Up @@ -145,7 +148,9 @@ abstract contract StorageContract is DecentralizedKV, ReentrancyGuardTransient {
}

/// @notice People can sent ETH to the contract.
function sendValue() public payable {}
function sendValue() public payable {
syntrust marked this conversation as resolved.
Show resolved Hide resolved
accPrepaidAmount += msg.value;
}

/// @notice Upfront payment for the next insertion
function upfrontPayment() public view virtual override returns (uint256) {
Expand Down Expand Up @@ -232,20 +237,20 @@ abstract contract StorageContract is DecentralizedKV, ReentrancyGuardTransient {
function _rewardMiner(uint256 _shardId, address _miner, uint256 _minedTs, uint256 _diff) internal {
// Mining is successful.
// Send reward to coinbase and miner.
(bool updatePrepaidTime, uint256 treasuryReward, uint256 minerReward) = _miningReward(_shardId, _minedTs);
(bool updatePrepaidTime, uint256 prepaidAmountSaved, uint256 treasuryReward, uint256 minerReward) =
_miningReward(_shardId, _minedTs);
if (updatePrepaidTime) {
prepaidLastMineTime = _minedTs;
}

accPrepaidAmount += prepaidAmountSaved + treasuryReward;
// Update mining info.
MiningLib.update(infos[_shardId], _minedTs, _diff);

require(treasuryReward + minerReward <= address(this).balance, "StorageContract: not enough balance");
require(minerReward <= address(this).balance, "StorageContract: not enough balance");
// Actually `transfer` is limited by the amount of gas allocated, which is not sufficient to enable reentrancy attacks.
// However, this behavior may restrict the extensibility of scenarios where the receiver is a contract that requires
// additional gas for its fallback functions of proper operations.
// Therefore, we use `ReentrancyGuard` in case `call` replaces `transfer` in the future.
payable(treasury).transfer(treasuryReward);
payable(_miner).transfer(minerReward);
emit MinedBlock(_shardId, _diff, infos[_shardId].blockMined, _minedTs, _miner, minerReward);
}
Expand All @@ -254,32 +259,40 @@ abstract contract StorageContract is DecentralizedKV, ReentrancyGuardTransient {
/// @param _shardId The shard id.
/// @param _minedTs The mined timestamp.
/// @return updatePrepaidTime Whether to update the prepaid time.
/// @return prepaidAmountSaved The capped part of prepaid amount.
/// @return treasuryReward The treasury reward.
/// @return minerReward The miner reward.
function _miningReward(uint256 _shardId, uint256 _minedTs) internal view returns (bool, uint256, uint256) {
function _miningReward(uint256 _shardId, uint256 _minedTs)
internal
view
returns (bool, uint256, uint256, uint256)
{
MiningLib.MiningInfo storage info = infos[_shardId];
uint256 lastShardIdx = kvEntryCount > 0 ? (kvEntryCount - 1) >> SHARD_ENTRY_BITS : 0;
uint256 reward = 0;
bool updatePrepaidTime = false;
uint256 prepaidAmountSaved = 0;
uint256 reward = 0;
if (_shardId < lastShardIdx) {
reward = _paymentIn(STORAGE_COST << SHARD_ENTRY_BITS, info.lastMineTime, _minedTs);
} else if (_shardId == lastShardIdx) {
reward = _paymentIn(STORAGE_COST * (kvEntryCount % (1 << SHARD_ENTRY_BITS)), info.lastMineTime, _minedTs);
// Additional prepaid for the last shard
if (prepaidLastMineTime < _minedTs) {
uint256 prepaidAmountCap =
STORAGE_COST * ((1 << SHARD_ENTRY_BITS) - kvEntryCount % (1 << SHARD_ENTRY_BITS));
if (prepaidAmountCap > prepaidAmount) {
prepaidAmountCap = prepaidAmount;
uint256 fullReward = _paymentIn(STORAGE_COST << SHARD_ENTRY_BITS, info.lastMineTime, _minedTs);
uint256 prepaidAmountIn = _paymentIn(prepaidAmount, prepaidLastMineTime, _minedTs);
uint256 rewardCap = fullReward - reward;
if (prepaidAmountIn > rewardCap) {
prepaidAmountSaved = prepaidAmountIn - rewardCap;
prepaidAmountIn = rewardCap;
}
reward += _paymentIn(prepaidAmountCap, prepaidLastMineTime, _minedTs);
reward += prepaidAmountIn;
updatePrepaidTime = true;
}
}

uint256 treasuryReward = (reward * TREASURY_SHARE) / 10000;
uint256 minerReward = reward - treasuryReward;
return (updatePrepaidTime, treasuryReward, minerReward);
return (updatePrepaidTime, prepaidAmountSaved, treasuryReward, minerReward);
}

/// @notice Get the mining reward.
Expand All @@ -288,7 +301,7 @@ abstract contract StorageContract is DecentralizedKV, ReentrancyGuardTransient {
/// @return The mining reward.
function miningReward(uint256 _shardId, uint256 _blockNum) public view returns (uint256) {
uint256 minedTs = _getMinedTs(_blockNum);
(,, uint256 minerReward) = _miningReward(_shardId, minedTs);
(,,, uint256 minerReward) = _miningReward(_shardId, minedTs);
return minerReward;
}

Expand Down Expand Up @@ -379,6 +392,14 @@ abstract contract StorageContract is DecentralizedKV, ReentrancyGuardTransient {
_rewardMiner(_shardId, _miner, mineTs, diff);
}

/// @notice Withdraw treasury fund
function withdraw(uint256 _amount) public {
require(accPrepaidAmount >= prepaidAmount + _amount, "StorageContract: not enough prepaid amount");
syntrust marked this conversation as resolved.
Show resolved Hide resolved
accPrepaidAmount -= _amount;
require(address(this).balance >= _amount, "StorageContract: not enough balance");
payable(treasury).transfer(_amount);
}

/// @notice Get the current block number
function _blockNumber() internal view virtual returns (uint256) {
return block.number;
Expand Down
76 changes: 71 additions & 5 deletions contracts/test/StorageContractTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,91 @@ contract StorageContractTest is Test {

function testMiningReward() public {
// no key-value stored on EthStorage, only use prepaid amount as the reward
(,, uint256 reward) = storageContract.miningRewards(0, 1);
(,,, uint256 reward) = storageContract.miningRewards(0, 1);
assertEq(reward, storageContract.paymentIn(PREPAID_AMOUNT, 0, 1));

// 1 key-value stored on EthStorage
storageContract.setKvEntryCount(1);
(,, reward) = storageContract.miningRewards(0, 1);
(,,, reward) = storageContract.miningRewards(0, 1);
assertEq(reward, storageContract.paymentIn(PREPAID_AMOUNT + STORAGE_COST * 1, 0, 1));

// 2 key-value stored on EthStorage
storageContract.setKvEntryCount(2);
(,, reward) = storageContract.miningRewards(0, 1);
(,,, reward) = storageContract.miningRewards(0, 1);
assertEq(reward, storageContract.paymentIn(PREPAID_AMOUNT + STORAGE_COST * 2, 0, 1));

// 3 key-value stored on EthStorage, but the reward is capped with 4 * STORAGE_COST
storageContract.setKvEntryCount(3);
(,, reward) = storageContract.miningRewards(0, 1);
(,,, reward) = storageContract.miningRewards(0, 1);
assertEq(reward, storageContract.paymentIn(PREPAID_AMOUNT + STORAGE_COST * 2, 0, 1));
}

function testWithdraw() public {
uint256 valueToSent = 30000000;
uint256 withdrawAmount = 10000001;

storageContract.sendValue{value: valueToSent}();
assertEq(storageContract.accPrepaidAmount(), valueToSent);

vm.expectRevert("StorageContract: not enough prepaid amount");
storageContract.withdraw(withdrawAmount);

withdrawAmount = 10000000;
storageContract.withdraw(withdrawAmount);
assertEq(storageContract.accPrepaidAmount(), valueToSent - withdrawAmount);
assertEq(storageContract.treasury().balance, withdrawAmount);
}

function testWithdrawRewardMiner() public {
uint256 valueToSent = 50000000;
uint256 withdrawAmount = 8000000;
uint256 mineTs = 10000;
address miner = vm.addr(2);
storageContract.sendValue{value: valueToSent}();

// a little half
storageContract.setKvEntryCount(1);
uint256 reward = storageContract.paymentIn(STORAGE_COST, 0, mineTs);
uint256 prepaidReward = storageContract.paymentIn(PREPAID_AMOUNT, 0, mineTs);
reward += prepaidReward;
uint256 treasureReward = (reward * storageContract.treasuryShare()) / 10000;
uint256 minerReward = reward - treasureReward;

storageContract.rewardMiner(0, miner, mineTs, 1);
assertEq(miner.balance, minerReward);
assertEq(storageContract.accPrepaidAmount(), valueToSent + treasureReward);

storageContract.withdraw(withdrawAmount);
assertEq(storageContract.accPrepaidAmount(), valueToSent + treasureReward - withdrawAmount);
assertEq(storageContract.treasury().balance, withdrawAmount);
assertEq(address(storageContract).balance, valueToSent - minerReward - withdrawAmount);
}

function testWithdrawRewardMinerSaved() public {
uint256 valueToSent = 50000000;
uint256 withdrawAmount = 8000000;
uint256 mineTs = 10000;
address miner = vm.addr(2);
storageContract.sendValue{value: valueToSent}();

// more than half
storageContract.setKvEntryCount(3);
uint256 rewardFull = storageContract.paymentIn(STORAGE_COST << (SHARD_SIZE_BITS - MAX_KV_SIZE), 0, mineTs);
(, uint256 saved,, uint256 reward) = storageContract.miningRewards(0, mineTs);
assertEq(rewardFull, reward);
uint256 treasureReward = (reward * storageContract.treasuryShare()) / 10000;
uint256 minerReward = reward - treasureReward;

storageContract.rewardMiner(0, miner, mineTs, 1);
assertEq(miner.balance, minerReward);
assertEq(storageContract.accPrepaidAmount(), valueToSent + treasureReward + saved);

storageContract.withdraw(withdrawAmount);
assertEq(storageContract.accPrepaidAmount(), valueToSent + treasureReward + saved - withdrawAmount);
assertEq(storageContract.treasury().balance, withdrawAmount);
assertEq(address(storageContract).balance, valueToSent - minerReward - withdrawAmount);
}

function testRewardMiner() public {
address miner = vm.addr(2);
uint256 mineTs = 10000;
Expand All @@ -55,7 +121,7 @@ contract StorageContractTest is Test {

vm.deal(address(storageContract), 1000);

(,, uint256 reward) = storageContract.miningRewards(0, mineTs);
(,,, uint256 reward) = storageContract.miningRewards(0, mineTs);
storageContract.rewardMiner(0, miner, mineTs, diff);
(uint256 l, uint256 d, uint256 b) = storageContract.infos(0);
assertEq(l, mineTs);
Expand Down
2 changes: 1 addition & 1 deletion contracts/test/TestStorageContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ contract TestStorageContract is StorageContract {
return _paymentIn(_x, _fromTs, _toTs);
}

function miningRewards(uint256 _shardId, uint256 _minedTs) public view returns (bool, uint256, uint256) {
function miningRewards(uint256 _shardId, uint256 _minedTs) public view returns (bool, uint256, uint256, uint256) {
return _miningReward(_shardId, _minedTs);
}

Expand Down
Loading