diff --git a/404.html b/404.html index b220ffa30..9e517a9e2 100644 --- a/404.html +++ b/404.html @@ -4,7 +4,7 @@
import { BaseEvent } from 'lisk-sdk';
export const newHelloEventSchema = {
$id: '/hello/events/new_hello',
type: 'object',
required: ['senderAddress', 'message'],
properties: {
senderAddress: {
dataType: 'bytes',
fieldNumber: 1,
},
message: {
dataType: 'string',
fieldNumber: 2,
},
},
};
export interface NewHelloEventData {
senderAddress: Buffer;
message: string;
}
export class NewHelloEvent extends BaseEvent<NewHelloEventData> {
public schema = newHelloEventSchema;
}
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
pragma solidity ^0.8.20;
contract Hello {
/** State variables */
// State variable for the Hello messages
mapping(address => string) public message;
// State variable for the message counter
uint32 public counter = 0;
/** Events */
// Event for new Hello messages
event NewHello(address indexed sender, string message);
}
The module-specific configurations, which resided in the config.json
on Lisk L1, are now part of the smart contract itself, and defined as state variables.
// Blacklist of words that are not allowed in the Hello message
string[] public blacklist = ["word1","word2"];
// Maximum length of the Hello message
uint32 public maxLength = 200;
// Minimum length of the Hello message
uint32 public minlength = 3
To edit the configuration options of the Hello module, we implement the following functions in the Hello contract:
The module-specific configurations, which resided in the config.json
on Lisk L1, are now part of the smart contract itself and are defined as state variables.
// Blacklist of words that are not allowed in the Hello message
string[] public blacklist = ["word1","word2"];
// Maximum length of the Hello message
uint32 public maxLength = 200;
// Minimum length of the Hello message
uint32 public minlength = 3;
To edit the configuration options of the Hello module, we implement the following functions in the Hello contract:
setBlacklist()
to configure the blacklist of words that are not allowed in the Hello message.setMinMaxMessageLength()
to configure the minimum and maximum length of the Hello message.// Function to configure the blacklist
function setBlacklist(string[] memory _newBlackList) public onlyOwner {
blacklist = _newBlackList;
}
// Function to configure min/max message length
function setMinMaxMessageLength(uint32 _newMinLength,uint32 _newMaxLength) public onlyOwner {
minlength = _newMinLength;
maxLength = _newMaxLength;
}
As seen in the above code snippet, we add the following modifiers to the functions:
onlyOwner
to check that the caller is the owner of the contract.
This is a custom modifier that we need to implement in the contract manually, as shown in the example below.To set the owner of the contract, we add a new state variable owner
and a constructor which sets the owner
variable to the account address that deploys the contract.
For updating the owner of the smart contract, you can imeplemnt a corresponding function setOwner()
and use the onlyOwner
modifier to ensure that only the current owner can call this function.
Finally, we can check for the message sender being the owner of the contract in the onlyOwner
modifier which is used for the setBlacklist()
and setMinMaxMessageLength()
functions.
// Address of the contract owner
address public immutable owner;
constructor() {
// Set the transaction sender as the owner of the contract.
owner = msg.sender;
}
/** Modifiers */
// Modifier to check that the caller is the owner of the contract.
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
To verify the Hello message, we implement custom modifiers in the contract.
Inside of the modifiers, we check the length of the message and if it contains any blacklisted words, like it was done in the verify()
method of the Lisk L1 Hello module.
Conveniently check the length of Hello messages in the validLength
modifier like this:
// Validate message length
modifier validLength(string memory _message) {
require(bytes(_message).length >= minlength, "Message too short");
require(bytes(_message).length <= maxLength, "Message too long");
_;
}
To check if the message contains any blacklisted words, we implement the validWords
modifier in the contract.
// Validate message content
modifier validWords(string memory _message) {
bytes memory whereBytes = bytes (_message);
for (uint h = 0; h < blacklist.length; h++) {
bool found = false;
bytes memory whatBytes = bytes (blacklist[h]);
for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {
bool flag = true;
for (uint j = 0; j < whatBytes.length; j++)
if (whereBytes [i + j] != whatBytes [j]) {
flag = false;
break;
}
if (flag) {
found = true;
break;
}
}
require (!found, "Message contains blacklisted word");
}
_;
}
To migrate the createHello command execution, we implement the createHello()
function in the contract.
Inside of this function, we save the message of the sender in the message
mapping under the sender address.
The sender address is a global variable in Solidity and can be accessed with msg.sender
.
Additionally, we increment the Hello message counter by +1, and emit the NewHello
event, like it was done in the execute()
method of the Lisk L1 Hello module previously.
The validMessage()
modifier the we defined above in the Verification section is used to check if the message is valid, before createHello()
function is executed.
// Function to create a new Hello message
function createHello(string calldata _message) public validMessage(_message) {
message[msg.sender] = _message;
counter+=1;
emit NewHello(msg.sender, _message);
}
To set the owner of the contract, we add a new state variable owner
, and a constructor which sets the owner
variable to the account address that deploys the contract.
To update the smart contract owner, you can implement a corresponding function setOwner()
, and use the onlyOwner
modifier to ensure that only the current owner can call this function.
Finally, we can check for the message sender being the owner of the contract in the onlyOwner
modifier which is used for the setBlacklist()
and setMinMaxMessageLength()
functions.
// Address of the contract owner
address public immutable owner;
constructor() {
// Set the transaction sender as the owner of the contract.
owner = msg.sender;
}
/** Modifiers */
// Modifier to check that the caller is the owner of the contract.
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
To verify the Hello message, we implement custom modifiers in the contract.
Inside the modifiers, we check the length of the message and if it contains any blacklisted words like it was done in the verify()
method of the Lisk L1 Hello module.
Conveniently check the length of Hello messages in the validLength
modifier like this:
// Validate message length
modifier validLength(string memory _message) {
require(bytes(_message).length >= minlength, "Message too short");
require(bytes(_message).length <= maxLength, "Message too long");
_;
}
To check if the message contains any blacklisted words, we implement the validWords
modifier in the contract.
// Validate message content
modifier validWords(string memory _message) {
bytes memory whereBytes = bytes (_message);
for (uint h = 0; h < blacklist.length; h++) {
bool found = false;
bytes memory whatBytes = bytes (blacklist[h]);
for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {
bool flag = true;
for (uint j = 0; j < whatBytes.length; j++)
if (whereBytes [i + j] != whatBytes [j]) {
flag = false;
break;
}
if (flag) {
found = true;
break;
}
}
require (!found, "Message contains blacklisted word");
}
_;
}
To migrate the createHello command execution, we implement the createHello()
function in the contract.
Inside this function, we save the message of the sender in the message
mapping under the sender address.
The sender address is a global variable in Solidity and can be accessed with msg.sender
.
Additionally, we increment the Hello message counter by 1
and emit the NewHello
event, like it was done in the execute()
method of the Lisk L1 Hello module previously.
The validMessage()
modifier that we defined above in the Verification section is used to check if the message is valid before the createHello()
function is executed.
// Function to create a new Hello message
function createHello(string calldata _message) public validMessage(_message) {
message[msg.sender] = _message;
counter+=1;
emit NewHello(msg.sender, _message);
}
/* eslint-disable class-methods-use-this */
import {
BaseCommand,
CommandVerifyContext,
CommandExecuteContext,
VerificationResult,
VerifyStatus,
} from 'lisk-sdk';
import { createHelloSchema } from '../schema';
import { MessageStore } from '../stores/message';
import { counterKey, CounterStore, CounterStoreData } from '../stores/counter';
import { ModuleConfig } from '../types';
import { NewHelloEvent } from '../events/new_hello';
interface Params {
message: string;
}
export class CreateHelloCommand extends BaseCommand {
public schema = createHelloSchema;
private _blacklist!: string[];
// eslint-disable-next-line @typescript-eslint/require-await
public async init(config: ModuleConfig): Promise<void> {
// Set _blacklist to the value of the blacklist defined in the module config
this._blacklist = config.blacklist;
// Set the max message length to the value defined in the module config
this.schema.properties.message.maxLength = config.maxMessageLength;
// Set the min message length to the value defined in the module config
this.schema.properties.message.minLength = config.minMessageLength;
}
// eslint-disable-next-line @typescript-eslint/require-await
public async verify(context: CommandVerifyContext<Params>): Promise<VerificationResult> {
let validation: VerificationResult;
const wordList = context.params.message.split(" ");
const found = this._blacklist.filter(value => wordList.includes(value));
if (found.length > 0) {
context.logger.info("==== FOUND: Message contains a blacklisted word ====");
throw new Error(
`Illegal word in hello message: ${ found.toString()}`
);
} else {
context.logger.info("==== NOT FOUND: Message contains no blacklisted words ====");
validation = {
status: VerifyStatus.OK
};
}
return validation;
}
public async execute(context: CommandExecuteContext<Params>): Promise<void> {
// 1. Get account data of the sender of the Hello transaction.
const { senderAddress } = context.transaction;
// 2. Get message and counter stores.
const messageSubstore = this.stores.get(MessageStore);
const counterSubstore = this.stores.get(CounterStore);
// 3. Save the Hello message to the message store, using the senderAddress as key, and the message as value.
await messageSubstore.set(context, senderAddress, {
message: context.params.message,
});
// 3. Get the Hello counter from the counter store.
let helloCounter: CounterStoreData;
try {
helloCounter = await counterSubstore.get(context, counterKey);
} catch (error) {
helloCounter = {
counter: 0,
}
}
// 5. Increment the Hello counter +1.
helloCounter.counter+=1;
// 6. Save the Hello counter to the counter store.
await counterSubstore.set(context, counterKey, helloCounter);
// 7. Emit a "New Hello" event
const newHelloEvent = this.events.get(NewHelloEvent);
newHelloEvent.add(context, {
senderAddress: context.transaction.senderAddress,
message: context.params.message
},[context.transaction.senderAddress]);
}
}
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
pragma solidity ^0.8.20;
contract Hello {
/** State variables */
// State variable for the Hello messages
mapping(address => string) public message;
// State variable for the message counter
uint32 public counter = 0;
// Address of the contract owner
address public immutable owner;
// Blacklist of words that are not allowed in the Hello message
string[] public blacklist = ["word1","word2"];
// Maximum length of the Hello message
uint32 public maxLength = 200;
// Minimum length of the Hello message
uint32 public minlength = 3;
constructor() {
// Set the transaction sender as the owner of the contract.
owner = msg.sender;
}
/** Modifiers */
// Modifier to check that the caller is the owner of the contract.
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// Validate message length
modifier validLength(string memory _message) {
require(bytes(_message).length >= minlength, "Message too short");
require(bytes(_message).length <= maxLength, "Message too long");
_;
}
// Validate message content
modifier validWords(string memory _message) {
bytes memory whereBytes = bytes (_message);
for (uint h = 0; h < blacklist.length; h++) {
bool found = false;
bytes memory whatBytes = bytes (blacklist[h]);
for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {
bool flag = true;
for (uint j = 0; j < whatBytes.length; j++)
if (whereBytes [i + j] != whatBytes [j]) {
flag = false;
break;
}
if (flag) {
found = true;
break;
}
}
require (!found, "Message contains blacklisted word");
}
_;
}
/** Events */
// Event for new Hello messages
event NewHello(address indexed sender, string message);
/** Functions */
// Function to configure the blacklist
function setBlacklist(string[] memory _newBlackList) public onlyOwner {
blacklist = _newBlackList;
}
// Function to configure min/max message length
function setMinMaxMessageLength(uint32 _newMinLength,uint32 _newMaxLength) public onlyOwner {
minlength = _newMinLength;
maxLength = _newMaxLength;
}
// Function to create a new Hello message
function createHello(string calldata _message) public validLength(_message) validWords(_message) {
message[msg.sender] = _message;
counter+=1;
emit NewHello(msg.sender, _message);
}
}
Migrate the module endpoints by implementing corresponding view functions in the contract as shown below.
export class HelloEndpoint extends BaseEndpoint {
public async getHelloCounter(ctx: ModuleEndpointContext): Promise<CounterStoreData> {
const counterSubStore = this.stores.get(CounterStore);
const helloCounter = await counterSubStore.get(
ctx,
counterKey,
);
return helloCounter;
}
public async getHello(ctx: ModuleEndpointContext): Promise<MessageStoreData> {
const messageSubStore = this.stores.get(MessageStore);
const { address } = ctx.params;
if (typeof address !== 'string') {
throw new Error('Parameter address must be a string.');
}
cryptography.address.validateLisk32Address(address);
const helloMessage = await messageSubStore.get(
ctx,
cryptography.address.getAddressFromLisk32Address(address),
);
return helloMessage;
}
}
For simple getters, it is sufficient to add the public
visibility modifier to the state variables.
-Public state variables can be accessed directly from external parties.
For more complex endpoints, you can implement corresponding view functions in the contract.
// State variable for the Hello messages
mapping(address => string) public message;
// State variable for the message counter
uint32 public counter = 0;
For more complex endpoints, you can implement corresponding view functions in the contract.
// State variable for the Hello messages
mapping(address => string) public message;
// State variable for the message counter
uint32 public counter = 0;
Now that we re-implemented the Hello module from Lisk L1 as a smart contract in Lisk L2, it is possible to directly deploy the Hello contract to Lisk L2 and interact with it.
Before deploying the smart contract to Lisk, it is recommended to test it locally by writing corresponding tests for the newly created smart contract. @@ -66,7 +66,7 @@
By testing the smart contract, you can verify that the smart contract behaves as expected and that it is free of bugs, before deploying it to Lisk.
Foundry provides a testing framework to support you in writing tests for smart contracts. See Tests - Foundry Book for examples and references regarding the testing framework.
-To test the Hello smart contract, create a new file Hello.t.sol
under test/
and add the following content:
To test the Hello smart contract, create a new file Hello.t.sol
under test/
, and add the following content:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {Hello} from "../src/Hello.sol";
contract HelloTest is Test {
Hello public hello;
address alice = makeAddr("alice");
event NewHello(address indexed sender, string message);
function setUp() public {
hello = new Hello();
}
function test_CreateHello() public {
string memory message = "Hello World";
// Expect NewHello event
vm.expectEmit(true,false,false,false);
emit NewHello(address(alice), message);
// Create a new Hello message
hoax(alice, 100 ether);
hello.createHello(message);
// Check the message
assertEq(hello.message(alice),message);
// Check if counter = 1
assertEq(hello.counter(),1);
}
function test_MinLength() public {
vm.expectRevert("Message too short");
hello.createHello("Hi");
}
function test_MaxLength() public {
vm.expectRevert("Message too long");
hello.createHello("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean porta neque eget elit tristique pharetra. Pellentesque tempus sollicitudin tortor, ut tempus diam. Nulla facilisi. Donec at neque sapien.");
}
function test_Blacklist() public {
vm.expectRevert("Message contains blacklisted word");
hello.createHello("Hello word1");
}
function test_SetBlacklist() public {
// Create a temporary dynamic array of strings
string[] memory bl = new string[](3);
bl[0] = "word1";
bl[1] = "word3";
bl[2] = "word4";
hello.setBlacklist(bl);
string[] memory getBL = new string[](2);
getBL[0] = hello.blacklist(0);
getBL[1] = hello.blacklist(1);
assertEq(getBL[0], bl[0]);
assertEq(getBL[1], bl[1]);
}
function test_SetBlacklistNotOwner() public {
string[] memory bl = new string[](3);
bl[0] = "word1";
bl[1] = "word3";
bl[2] = "word4";
vm.expectRevert("Not owner");
hoax(alice, 100 ether);
hello.setBlacklist(bl);
}
function test_SetMinMaxMessageLength() public {
uint32 newMin = 1;
uint32 newMax = 500;
hello.setMinMaxMessageLength(newMin,newMax);
assertEq(hello.minLength(), newMin);
assertEq(hello.maxLength(), newMax);
}
function test_SetMinMaxMessageLengthNotOwner() public {
uint32 newMin = 1;
uint32 newMax = 500;
hoax(alice, 100 ether);
vm.expectRevert();
hello.setMinMaxMessageLength(newMin,newMax);
}
}
To run the tests, execute the following command:
forge test
The available endpoints for Lisk nodes include all Geth RPC endpoints, which also include all standard JSON-RPC API endpoints of Ethereum.
Free, rate limited RPC endpoints for the Lisk networks.
-Lisk Sepolia Testnet | Lisk Mainnet | |
---|---|---|
HTTP RPC | https://rpc.sepolia-api.lisk.com | https://rpc.api.lisk.com |
WS RPC | wss://ws.sepolia-api.lisk.com | wss://ws.api.lisk.com |
Lisk Sepolia Testnet | |
---|---|
HTTP RPC | https://rpc.sepolia-api.lisk.com |
WS RPC | wss://ws.sepolia-api.lisk.com |
dRPC is a decentralized Web3 infrastructure provider with a focus on resilience and latency. dRPC offers access to a distributed network of public nodes for Lisk. diff --git a/lisk-tools/bridges.html b/lisk-tools/bridges.html index b8609b3cd..f90a5de2f 100644 --- a/lisk-tools/bridges.html +++ b/lisk-tools/bridges.html @@ -4,7 +4,7 @@