diff --git a/contracts/EthStorageContractL2.sol b/contracts/EthStorageContractL2.sol index 7164417..6743311 100644 --- a/contracts/EthStorageContractL2.sol +++ b/contracts/EthStorageContractL2.sol @@ -20,6 +20,12 @@ interface IL1Block { function timestamp() external view returns (uint64); } +/// @title ISoulGasToken +/// @notice Interface for the SoulGasToken contract. +interface ISoulGasToken { + function chargeFromOrigin(uint256 _amount) external returns (uint256); +} + /// @custom:proxied /// @title EthStorageContractL2 /// @notice EthStorage contract that will be deployed on L2, and uses L1Block contract to mine. @@ -36,6 +42,9 @@ contract EthStorageContractL2 is EthStorageContract2 { /// @notice A slot to store both `blockLastUpdate` (left 224) and `blobsUpdated` (right 32) uint256 internal updateState; + /// @notice The address of the soul gas token. + address public soulGasToken; + /// @notice Constructs the EthStorageContractL2 contract. constructor( Config memory _config, @@ -47,6 +56,28 @@ contract EthStorageContractL2 is EthStorageContract2 { UPDATE_LIMIT = _updateLimit; } + /// @notice Set the soul gas token address for the contract. + function setSoulGasToken(address _soulGasToken) external onlyOwner { + soulGasToken = _soulGasToken; + } + + /// @inheritdoc StorageContract + function _checkAppend(uint256 _batchSize) internal virtual override { + uint256 kvEntryCountPrev = kvEntryCount - _batchSize; // kvEntryCount already increased + uint256 totalPayment = _upfrontPaymentInBatch(kvEntryCountPrev, _batchSize); + uint256 sgtCharged = 0; + if (soulGasToken != address(0)) { + sgtCharged = ISoulGasToken(soulGasToken).chargeFromOrigin(totalPayment); + } + require(msg.value >= totalPayment - sgtCharged, "EthStorageContractL2: not enough batch payment"); + + uint256 shardId = kvEntryCount >> SHARD_ENTRY_BITS; // shard id after the batch + if (shardId > (kvEntryCountPrev >> SHARD_ENTRY_BITS)) { + // Open a new shard and mark the shard is ready to mine. + infos[shardId].lastMineTime = _blockTs(); + } + } + /// @notice Get the current block number function _blockNumber() internal view virtual override returns (uint256) { return L1_BLOCK.number(); diff --git a/contracts/StorageContract.sol b/contracts/StorageContract.sol index 529b00c..ade3604 100644 --- a/contracts/StorageContract.sol +++ b/contracts/StorageContract.sol @@ -175,7 +175,7 @@ abstract contract StorageContract is DecentralizedKV { } /// @notice Upfront payment for a batch insertion - function _upfrontPaymentInBatch(uint256 _kvEntryCount, uint256 _batchSize) private view returns (uint256) { + function _upfrontPaymentInBatch(uint256 _kvEntryCount, uint256 _batchSize) internal view returns (uint256) { uint256 shardId = _kvEntryCount >> SHARD_ENTRY_BITS; uint256 totalEntries = _kvEntryCount + _batchSize; // include the batch to be put uint256 totalPayment = 0; diff --git a/contracts/test/EthStorageContractL2Test.t.sol b/contracts/test/EthStorageContractL2Test.t.sol index 2427af8..b5b2e9b 100644 --- a/contracts/test/EthStorageContractL2Test.t.sol +++ b/contracts/test/EthStorageContractL2Test.t.sol @@ -5,6 +5,12 @@ import "forge-std/Test.sol"; import "./TestEthStorageContractL2.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +contract SoulGasToken { + function chargeFromOrigin(uint256 _amount) external pure returns (uint256) { + return _amount; + } +} + contract EthStorageContractL2Test is Test { uint256 constant STORAGE_COST = 0; uint256 constant SHARD_SIZE_BITS = 19; @@ -76,4 +82,51 @@ contract EthStorageContractL2Test is Test { vm.expectRevert("EthStorageContractL2: exceeds update rate limit"); storageContract.putBlobs(keys, blobIdxs, lengths); } + + function testSGTPayment() public { + TestEthStorageContractL2 imp = new TestEthStorageContractL2( + StorageContract.Config(MAX_KV_SIZE, SHARD_SIZE_BITS, 2, 0, 0, 0), + block.timestamp, + 1500000000000000, + 0, + UPDATE_LIMIT + ); + bytes memory data = abi.encodeWithSelector( + storageContract.initialize.selector, 0, PREPAID_AMOUNT, 0, address(0x1), address(0x1) + ); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(address(imp), owner, data); + + TestEthStorageContractL2 l2Contract = TestEthStorageContractL2(address(proxy)); + + uint256 size = 6; + bytes32[] memory hashes = new bytes32[](size); + bytes32[] memory keys = new bytes32[](size); + uint256[] memory blobIdxs = new uint256[](size); + uint256[] memory lengths = new uint256[](size); + for (uint256 i = 0; i < size; i++) { + keys[i] = bytes32(uint256(i)); + hashes[i] = bytes32(uint256((i + 1) << 64)); + blobIdxs[i] = i; + lengths[i] = 10 + i * 10; + } + vm.blobhashes(hashes); + + vm.expectRevert("EthStorageContractL2: not enough batch payment"); + l2Contract.putBlobs{value: 1500000000000000 * 5}(keys, blobIdxs, lengths); + + l2Contract.putBlobs{value: 1500000000000000 * 6}(keys, blobIdxs, lengths); + + SoulGasToken sgt = new SoulGasToken(); + vm.prank(owner); + l2Contract.setSoulGasToken(address(sgt)); + + for (uint256 i = 0; i < size; i++) { + keys[i] = bytes32(uint256(i + 6)); + hashes[i] = bytes32(uint256((i + 1) << 64)); + blobIdxs[i] = i; + lengths[i] = 10 + i * 10; + } + vm.blobhashes(hashes); + l2Contract.putBlobs(keys, blobIdxs, lengths); + } }