// SPDX-License-Identifier: UNLICENSEDpragmasolidity ^0.8.13;import"ds-test/test.sol";import'src/SimpleNameRegister.sol';interface CheatCodes {functionprank(address) external;functionexpectRevert(bytescalldata) external;functionstartPrank(address) external;functionstopPrank() external;}contractSimpleNameRegisterTestisDSTest {// declare state var. SimpleNameRegister simpleNameRegister; CheatCodes cheats;address adversary;functionsetUp() public { simpleNameRegister =newSimpleNameRegister(); cheats =CheatCodes(HEVM_ADDRESS); adversary =0xE6A2e85916802210147e366D4431f5ca4dD51a78; }// user can register an available namefunctiontestRegisterName(stringmemory_testString) public { simpleNameRegister.registerName(_testString);bool success = (address(this) == simpleNameRegister.nameOwner(_testString));assertTrue(success); }// user can register an available name and relinquish itfunctiontestRelinquishName(stringmemory_testString) public { simpleNameRegister.registerName(_testString); simpleNameRegister.relinquishName(_testString);bool success = (simpleNameRegister.nameOwner(_testString) ==address(0));assertTrue(success); }// user cannot relinquish a name that does not belong to themfunctiontestRelinquishAsNotOwner(stringmemory_testString) public { simpleNameRegister.registerName(_testString); cheats.startPrank(adversary); cheats.expectRevert(bytes("The provided name does not belong to you!")); simpleNameRegister.relinquishName(_testString); cheats.stopPrank(); }// user cannot register a name that already has an ownerfunctiontestRegisterUnavailableName(stringmemory_testString) public { simpleNameRegister.registerName(_testString); cheats.startPrank(adversary); cheats.expectRevert(bytes("The provided name has already been registered!")); simpleNameRegister.registerName(_testString); cheats.stopPrank(); }}
// SPDX-License-Identifier: UNLICENSEDpragmasolidity ^0.8.13;import"ds-test/test.sol";import'src/SimpleNameRegister.sol';interface CheatCodes {functionprank(address) external;functionexpectRevert(bytescalldata) external;functionstartPrank(address) external;functionstopPrank() external;}abstractcontractStateZeroisDSTest { SimpleNameRegister public simpleNameRegister; CheatCodes cheats;functionsetUp() publicvirtual { simpleNameRegister =newSimpleNameRegister(); cheats =CheatCodes(HEVM_ADDRESS); }}contractStateZeroTestisStateZero {functiontestCannotRelease(stringmemory testString) public { cheats.expectRevert(bytes("Not your name!")); simpleNameRegister.release(testString); }functiontestRegister(stringmemory testString) public { simpleNameRegister.register(testString);bool success = (address(this) == simpleNameRegister.holder(testString));assertTrue(success); }}abstractcontractStateRegisteredisStateZero {address adversary;string name;functionsetUp() publicoverride { super.setUp(); adversary =0xE6A2e85916802210147e366D4431f5ca4dD51a78;// state transition name ='whale'; simpleNameRegister.register(name); }}contractStateRegisteredTestisStateRegistered {functiontestAdversaryCannotRegisterName() public { cheats.startPrank(adversary); cheats.expectRevert(bytes("Already registered!")); simpleNameRegister.register(name); cheats.stopPrank(); }functiontestAdversaryCannotReleaseName() public { cheats.startPrank(adversary); cheats.expectRevert(bytes("Not your name!")); simpleNameRegister.release(name); cheats.stopPrank(); }functiontestUserCannotRegisterOwnedName() public { cheats.expectRevert(bytes("Already registered!")); simpleNameRegister.register(name); }functiontestUserRelease() public { simpleNameRegister.release(name);bool success = (address(0) == simpleNameRegister.holder(name));assertTrue(success); }}
// SPDX-License-Identifier: UNLICENSEDpragmasolidity ^0.8.13;///@title An on-chain name registry///@author Calnixcontract SimpleNameRegister {/// @notice map a name to an address to identify current holder mapping (string=>address) public holder; /// @notice emit an event when a name is registered or releasedeventRegister(addressindexed holder, string name);eventRelease(addressindexed holder, string name);/// @notice user can register an available namefunctionregister(stringmemory name) public {require(holder[name] ==address(0),"Already registered!"); holder[name] = msg.sender;emitRegister(msg.sender, name); }/// @notice holder can release a name, making it availablefunctionrelease(stringmemory name) public {require(holder[name] == msg.sender,"Not your name!");delete holder[name];emitRelease(msg.sender, name); }}
State setups and transitions will be realized by abstract contracts. Here we have two states.
StateZero -> Inception, nothing has been done.
StateRegistered -> User has registered a name.
In StateZero, we will have to instantiate SimpleNameRegister, to interact with the contract for testing. We opt to instantiate cheats here as well.
abstract contract StateZero is DSTest{}
In StateRegistered, the user will register a name - which we observe in its setup function. Additionally, we instantiate an adversary address necessary for testing in this state.
State Zero: Inception
Create the state (abstract contract):
will contain the setUp() for State Zero
instantiate SimpleNameRegister
instantiate cheats (for use later in StateRegistered)
To execute the required tests in StateZero, create a contract StateZeroTest.
This will contain all test functions pertaining to said state.
The state will be realized by inheritance (StateZeroTest is StateZero).
contractStateZeroTestisStateZero {functiontestCannotRelease(stringmemory testString) public { cheats.expectRevert(bytes("Not your name!")); simpleNameRegister.release(testString); }functiontestRegister(stringmemory testString) public { simpleNameRegister.register(testString);bool success = (address(this) == simpleNameRegister.holder(testString));assertTrue(success); }}
By the way of inheritance, the setUp function and state variables contained within StateZero will be executed setting up the environment for test functions belonging to StateZeroTest.
State Transition
State transition occurs by inheritance between the abstract contracts.
A movement from state(0) -> state(1) is reflected as StateRegistered is StateZero.
abstractcontractStateRegisteredisStateZero {address adversary;string name;functionsetUp() publicoverride { super.setUp(); adversary =0xE6A2e85916802210147e366D4431f5ca4dD51a78;// state transition name ='whale'; simpleNameRegister.register(name); }}
The initial state is inherited and executed (super.setUp), building upon it, are the necessary actions to evolve into StateRegistered.
In this this case, those actions would be a user registering a name:
name = 'whale';
simpleNameRegister.register(name);
Now that StateRegistered has been realized, similar to before, we will create a separate contract composing of the test functions pertaining to this state.
Create the test contract for StateRegistered
contractStateRegisteredTestisStateRegistered {functiontestAdversaryCannotRegisterName() public { cheats.startPrank(adversary); cheats.expectRevert(bytes("Already registered!")); simpleNameRegister.register(name); cheats.stopPrank(); }functiontestAdversaryCannotReleaseName() public { cheats.startPrank(adversary); cheats.expectRevert(bytes("Not your name!")); simpleNameRegister.release(name); cheats.stopPrank(); }functiontestUserCannotRegisterOwnedName() public { cheats.expectRevert(bytes("Already registered!")); simpleNameRegister.register(name); }functiontestUserRelease() public { simpleNameRegister.release(name);bool success = (address(0) == simpleNameRegister.holder(name));assertTrue(success); }}
This approach prizes modularity which could prove useful in complicated testing situations.
Additionally, tests from an earlier state will not be repeated in a subsequent state as test contracts inherit state, not test functions.