From e05fc18ffc264493c199692544bf86167f5ebb0a Mon Sep 17 00:00:00 2001 From: anlithov Date: Fri, 2 May 2025 12:49:51 +0300 Subject: [PATCH 1/2] Shitpost cdn to walrus registry --- .gitmodules | 3 + examples/shitpost/contracts/.env.example | 3 + examples/shitpost/contracts/.gitignore | 14 ++ examples/shitpost/contracts/README.md | 11 ++ examples/shitpost/contracts/foundry.toml | 19 +++ examples/shitpost/contracts/lib/forge-std | 1 + .../contracts/script/DeployContract.sol | 20 +++ .../contracts/src/CDNToWalrusRegistry.sol | 27 ++++ .../shitpost/contracts/src/base/Context.sol | 28 ++++ .../shitpost/contracts/src/base/Ownable.sol | 100 ++++++++++++++ .../contracts/test/CDNToWalrusRegistry.t.sol | 130 ++++++++++++++++++ 11 files changed, 356 insertions(+) create mode 100644 examples/shitpost/contracts/.env.example create mode 100644 examples/shitpost/contracts/.gitignore create mode 100644 examples/shitpost/contracts/README.md create mode 100644 examples/shitpost/contracts/foundry.toml create mode 160000 examples/shitpost/contracts/lib/forge-std create mode 100644 examples/shitpost/contracts/script/DeployContract.sol create mode 100644 examples/shitpost/contracts/src/CDNToWalrusRegistry.sol create mode 100644 examples/shitpost/contracts/src/base/Context.sol create mode 100644 examples/shitpost/contracts/src/base/Ownable.sol create mode 100644 examples/shitpost/contracts/test/CDNToWalrusRegistry.t.sol diff --git a/.gitmodules b/.gitmodules index c65a5965..b44fa739 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "contracts/lib/forge-std"] path = contracts/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "examples/shitpost/contracts/lib/forge-std"] + path = examples/shitpost/contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/examples/shitpost/contracts/.env.example b/examples/shitpost/contracts/.env.example new file mode 100644 index 00000000..af81b897 --- /dev/null +++ b/examples/shitpost/contracts/.env.example @@ -0,0 +1,3 @@ +DEPLOYER_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000 +DEPLOYER_ADDRESS=0x0000000000000000000000000000000000000000 +RPC_URL=https://rpc.00000000000000000 \ No newline at end of file diff --git a/examples/shitpost/contracts/.gitignore b/examples/shitpost/contracts/.gitignore new file mode 100644 index 00000000..85198aaa --- /dev/null +++ b/examples/shitpost/contracts/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/examples/shitpost/contracts/README.md b/examples/shitpost/contracts/README.md new file mode 100644 index 00000000..f0b8d882 --- /dev/null +++ b/examples/shitpost/contracts/README.md @@ -0,0 +1,11 @@ +# Shitpost utility contracts + + +## Deploy + +``` +source .env && forge script DeployContract \ +--rpc-url $RPC_URL +--broadcast \ +--private-key $DEPLOYER_PRIVATE_KEY +``` \ No newline at end of file diff --git a/examples/shitpost/contracts/foundry.toml b/examples/shitpost/contracts/foundry.toml new file mode 100644 index 00000000..af8107f3 --- /dev/null +++ b/examples/shitpost/contracts/foundry.toml @@ -0,0 +1,19 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +remappings = [ + "@forge/=lib/forge-std/src/" +] + +fs_permissions = [{ access = "read-write", path = "./deployments/"}] + +solc = "0.8.28" +evm_version = "berlin" +optimizer = true +optimizer_runs = 1 +bytecode_hash = "none" +cbor_metadata = false + + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/examples/shitpost/contracts/lib/forge-std b/examples/shitpost/contracts/lib/forge-std new file mode 160000 index 00000000..77041d2c --- /dev/null +++ b/examples/shitpost/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 77041d2ce690e692d6e03cc812b57d1ddaa4d505 diff --git a/examples/shitpost/contracts/script/DeployContract.sol b/examples/shitpost/contracts/script/DeployContract.sol new file mode 100644 index 00000000..76eefbdf --- /dev/null +++ b/examples/shitpost/contracts/script/DeployContract.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.8.25; + +import "forge-std/Script.sol"; +import "../src/CDNToWalrusRegistry.sol"; +import {console} from "@forge/console.sol"; + +contract DeployContract is Script { + function run() external { + uint256 deployerPK = vm.envUint("DEPLOYER_PRIVATE_KEY"); + + vm.startBroadcast(deployerPK); + + CDNToWalrusRegistry registry = new CDNToWalrusRegistry(); + + vm.stopBroadcast(); + + console.log("CDNToWalrusRegistry deployed at:", address(registry)); + console.log("Owner set to:", registry.owner()); + } +} diff --git a/examples/shitpost/contracts/src/CDNToWalrusRegistry.sol b/examples/shitpost/contracts/src/CDNToWalrusRegistry.sol new file mode 100644 index 00000000..6be08f37 --- /dev/null +++ b/examples/shitpost/contracts/src/CDNToWalrusRegistry.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.8.25; + +import {Ownable} from "./base/Ownable.sol"; + +contract CDNToWalrusRegistry is Ownable { + mapping(bytes32 => bytes32) public cdnIdToWalrusId; + mapping(bytes32 => bytes32) public walrusIdToCdnId; + + event CDNWalrusRecordCreated(bytes32 indexed cdnId, bytes32 indexed walrusId); + + constructor() Ownable(msg.sender) {} + + function setRecord(bytes32 cdnId, bytes32 walrusId) public onlyOwner { + cdnIdToWalrusId[cdnId] = walrusId; + walrusIdToCdnId[walrusId] = cdnId; + + emit CDNWalrusRecordCreated(cdnId, walrusId); + } + + function getWalrusIdByCdnId(bytes32 cdnId) public view returns (bytes32) { + return cdnIdToWalrusId[cdnId]; + } + + function getCdnIdByWalrusId(bytes32 walrusId) public view returns (bytes32) { + return walrusIdToCdnId[walrusId]; + } +} diff --git a/examples/shitpost/contracts/src/base/Context.sol b/examples/shitpost/contracts/src/base/Context.sol new file mode 100644 index 00000000..4e535fe0 --- /dev/null +++ b/examples/shitpost/contracts/src/base/Context.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + + function _contextSuffixLength() internal view virtual returns (uint256) { + return 0; + } +} diff --git a/examples/shitpost/contracts/src/base/Ownable.sol b/examples/shitpost/contracts/src/base/Ownable.sol new file mode 100644 index 00000000..09806980 --- /dev/null +++ b/examples/shitpost/contracts/src/base/Ownable.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) + +pragma solidity ^0.8.20; + +import {Context} from "./Context.sol"; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * The initial owner is set to the address provided by the deployer. This can + * later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OwnableUnauthorizedAccount(address account); + + /** + * @dev The owner is not a valid owner account. (eg. `address(0)`) + */ + error OwnableInvalidOwner(address owner); + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the address provided by the deployer as the initial owner. + */ + constructor(address initialOwner) { + if (initialOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(initialOwner); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + if (owner() != _msgSender()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby disabling any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + if (newOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} diff --git a/examples/shitpost/contracts/test/CDNToWalrusRegistry.t.sol b/examples/shitpost/contracts/test/CDNToWalrusRegistry.t.sol new file mode 100644 index 00000000..97824e63 --- /dev/null +++ b/examples/shitpost/contracts/test/CDNToWalrusRegistry.t.sol @@ -0,0 +1,130 @@ +pragma solidity ^0.8.25; + +import {Test} from "forge-std/Test.sol"; +import "../src/CDNToWalrusRegistry.sol"; +import {console} from "@forge/console.sol"; + +contract CDNToWalrusRegistryTest is Test { + CDNToWalrusRegistry registry; + address owner = address(0x1); + address nonOwner = address(0x2); + + // The specific IDs provided + bytes32 constant WALRUS_ID = 0x503d77961eb14a308a9b87d92c7898c19937cd267b3d9ded18df194a6d7f56fc; // base64url decode of "Pz3wmmi6F1KMmrN9QcGR_fnqyg4cupFI-nS-WSW2d1c" + bytes32 constant CDN_ID = 0xf5c2099e69ee4bdc4e9b87d92c7898c19937cd267b3d9ded18df194a6d7f56fc; + + // Define the event signature for testing + event CDNWalrusRecordCreated(bytes32 indexed cdnId, bytes32 indexed walrusId); + + function setUp() public { + vm.startPrank(owner); + registry = new CDNToWalrusRegistry(); + vm.stopPrank(); + } + + function testConstructor() public { + assertEq(registry.owner(), owner, "Owner should be set to the deployer"); + } + + function testSetRecord() public { + vm.startPrank(owner); + registry.setRecord(CDN_ID, WALRUS_ID); + vm.stopPrank(); + + assertEq(registry.cdnIdToWalrusId(CDN_ID), WALRUS_ID, "CDN ID should map to Walrus ID"); + assertEq(registry.walrusIdToCdnId(WALRUS_ID), CDN_ID, "Walrus ID should map to CDN ID"); + } + + function testSetRecordEmitsEvent() public { + vm.startPrank(owner); + + vm.expectEmit(true, true, false, true); + emit CDNWalrusRecordCreated(CDN_ID, WALRUS_ID); + registry.setRecord(CDN_ID, WALRUS_ID); + + vm.stopPrank(); + } + + function testSetRecordFailsForNonOwner() public { + vm.startPrank(nonOwner); + + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, nonOwner)); + registry.setRecord(CDN_ID, WALRUS_ID); + + console.logBytes32(CDN_ID); + console.logBytes32(WALRUS_ID); + + vm.stopPrank(); + } + + function testGetters() public { + vm.startPrank(owner); + registry.setRecord(CDN_ID, WALRUS_ID); + vm.stopPrank(); + + assertEq(registry.getWalrusIdByCdnId(CDN_ID), WALRUS_ID, "getWalrusIdByCdnId should return correct Walrus ID"); + assertEq(registry.getCdnIdByWalrusId(WALRUS_ID), CDN_ID, "getCdnIdByWalrusId should return correct CDN ID"); + } + + function testTransferOwnership() public { + vm.startPrank(owner); + registry.transferOwnership(nonOwner); + vm.stopPrank(); + + assertEq(registry.owner(), nonOwner, "Ownership should be transferred to nonOwner"); + + // Now nonOwner should be able to add records + vm.startPrank(nonOwner); + registry.setRecord(CDN_ID, WALRUS_ID); + vm.stopPrank(); + + assertEq(registry.cdnIdToWalrusId(CDN_ID), WALRUS_ID, "New owner should be able to add records"); + } + + function testTransferOwnershipFailsForNonOwner() public { + vm.startPrank(nonOwner); + + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, nonOwner)); + registry.transferOwnership(nonOwner); + + vm.stopPrank(); + } + + function testMultipleRecords() public { + bytes32 cdnId1 = keccak256("cdn1"); + bytes32 walrusId1 = keccak256("walrus1"); + bytes32 cdnId2 = keccak256("cdn2"); + bytes32 walrusId2 = keccak256("walrus2"); + + vm.startPrank(owner); + + registry.setRecord(cdnId1, walrusId1); + registry.setRecord(cdnId2, walrusId2); + + vm.stopPrank(); + + assertEq(registry.cdnIdToWalrusId(cdnId1), walrusId1, "First record should be correct"); + assertEq(registry.walrusIdToCdnId(walrusId1), cdnId1, "First record inverse should be correct"); + + assertEq(registry.cdnIdToWalrusId(cdnId2), walrusId2, "Second record should be correct"); + assertEq(registry.walrusIdToCdnId(walrusId2), cdnId2, "Second record inverse should be correct"); + } + + function testOverwriteRecord() public { + bytes32 cdnId = keccak256("cdn"); + bytes32 walrusId1 = keccak256("walrus1"); + bytes32 walrusId2 = keccak256("walrus2"); + + vm.startPrank(owner); + + // Add initial mapping + registry.setRecord(cdnId, walrusId1); + assertEq(registry.cdnIdToWalrusId(cdnId), walrusId1, "Initial record should be set"); + + // Overwrite with new mapping + registry.setRecord(cdnId, walrusId2); + assertEq(registry.cdnIdToWalrusId(cdnId), walrusId2, "Record should be overwritten"); + + vm.stopPrank(); + } +} \ No newline at end of file From 15a2dc0eae119d7b8c71a1186541df441e74b020 Mon Sep 17 00:00:00 2001 From: anlithov Date: Fri, 2 May 2025 13:13:28 +0300 Subject: [PATCH 2/2] Private maps --- .../contracts/src/CDNToWalrusRegistry.sol | 4 ++-- .../contracts/test/CDNToWalrusRegistry.t.sol | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/shitpost/contracts/src/CDNToWalrusRegistry.sol b/examples/shitpost/contracts/src/CDNToWalrusRegistry.sol index 6be08f37..64b20820 100644 --- a/examples/shitpost/contracts/src/CDNToWalrusRegistry.sol +++ b/examples/shitpost/contracts/src/CDNToWalrusRegistry.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.25; import {Ownable} from "./base/Ownable.sol"; contract CDNToWalrusRegistry is Ownable { - mapping(bytes32 => bytes32) public cdnIdToWalrusId; - mapping(bytes32 => bytes32) public walrusIdToCdnId; + mapping(bytes32 => bytes32) private cdnIdToWalrusId; + mapping(bytes32 => bytes32) private walrusIdToCdnId; event CDNWalrusRecordCreated(bytes32 indexed cdnId, bytes32 indexed walrusId); diff --git a/examples/shitpost/contracts/test/CDNToWalrusRegistry.t.sol b/examples/shitpost/contracts/test/CDNToWalrusRegistry.t.sol index 97824e63..ae4daa44 100644 --- a/examples/shitpost/contracts/test/CDNToWalrusRegistry.t.sol +++ b/examples/shitpost/contracts/test/CDNToWalrusRegistry.t.sol @@ -31,8 +31,8 @@ contract CDNToWalrusRegistryTest is Test { registry.setRecord(CDN_ID, WALRUS_ID); vm.stopPrank(); - assertEq(registry.cdnIdToWalrusId(CDN_ID), WALRUS_ID, "CDN ID should map to Walrus ID"); - assertEq(registry.walrusIdToCdnId(WALRUS_ID), CDN_ID, "Walrus ID should map to CDN ID"); + assertEq(registry.getWalrusIdByCdnId(CDN_ID), WALRUS_ID, "CDN ID should map to Walrus ID"); + assertEq(registry.getCdnIdByWalrusId(WALRUS_ID), CDN_ID, "Walrus ID should map to CDN ID"); } function testSetRecordEmitsEvent() public { @@ -78,7 +78,7 @@ contract CDNToWalrusRegistryTest is Test { registry.setRecord(CDN_ID, WALRUS_ID); vm.stopPrank(); - assertEq(registry.cdnIdToWalrusId(CDN_ID), WALRUS_ID, "New owner should be able to add records"); + assertEq(registry.getWalrusIdByCdnId(CDN_ID), WALRUS_ID, "New owner should be able to add records"); } function testTransferOwnershipFailsForNonOwner() public { @@ -103,11 +103,11 @@ contract CDNToWalrusRegistryTest is Test { vm.stopPrank(); - assertEq(registry.cdnIdToWalrusId(cdnId1), walrusId1, "First record should be correct"); - assertEq(registry.walrusIdToCdnId(walrusId1), cdnId1, "First record inverse should be correct"); + assertEq(registry.getWalrusIdByCdnId(cdnId1), walrusId1, "First record should be correct"); + assertEq(registry.getCdnIdByWalrusId(walrusId1), cdnId1, "First record inverse should be correct"); - assertEq(registry.cdnIdToWalrusId(cdnId2), walrusId2, "Second record should be correct"); - assertEq(registry.walrusIdToCdnId(walrusId2), cdnId2, "Second record inverse should be correct"); + assertEq(registry.getWalrusIdByCdnId(cdnId2), walrusId2, "Second record should be correct"); + assertEq(registry.getCdnIdByWalrusId(walrusId2), cdnId2, "Second record inverse should be correct"); } function testOverwriteRecord() public { @@ -119,11 +119,11 @@ contract CDNToWalrusRegistryTest is Test { // Add initial mapping registry.setRecord(cdnId, walrusId1); - assertEq(registry.cdnIdToWalrusId(cdnId), walrusId1, "Initial record should be set"); + assertEq(registry.getWalrusIdByCdnId(cdnId), walrusId1, "Initial record should be set"); // Overwrite with new mapping registry.setRecord(cdnId, walrusId2); - assertEq(registry.cdnIdToWalrusId(cdnId), walrusId2, "Record should be overwritten"); + assertEq(registry.getWalrusIdByCdnId(cdnId), walrusId2, "Record should be overwritten"); vm.stopPrank(); }