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

Only emit shard events on change #34

Merged
merged 2 commits into from
Dec 23, 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
28 changes: 17 additions & 11 deletions src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,12 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
*/
function setShard(TssKey calldata publicKey) external {
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ShardStore.getMainStorage().register(publicKey);
TssKey[] memory keys = new TssKey[](1);
keys[0] = publicKey;
emit ShardsRegistered(keys);
bool isSuccess = ShardStore.getMainStorage().register(publicKey);
if (isSuccess) {
TssKey[] memory keys = new TssKey[](1);
keys[0] = publicKey;
emit ShardsRegistered(keys);
}
}

/**
Expand All @@ -456,19 +458,23 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 {
*/
function revokeShard(TssKey calldata publicKey) external {
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ShardStore.getMainStorage().revoke(publicKey);
TssKey[] memory keys = new TssKey[](1);
keys[0] = publicKey;
emit ShardsUnregistered(keys);
bool isSuccess = ShardStore.getMainStorage().revoke(publicKey);
if (isSuccess) {
TssKey[] memory keys = new TssKey[](1);
keys[0] = publicKey;
emit ShardsUnregistered(keys);
}
}

/**
* @dev Revoke Shards in batch.
*/
function revokeShard(TssKey[] calldata publicKeys) external {
function revokeShards(TssKey[] calldata publicKeys) external {
require(msg.sender == ERC1967.getAdmin(), "unauthorized");
ShardStore.getMainStorage().revokeKeys(publicKeys);
emit ShardsUnregistered(publicKeys);
TssKey[] memory revokedKeys = ShardStore.getMainStorage().revokeKeys(publicKeys);
if (revokedKeys.length > 0) {
emit ShardsUnregistered(revokedKeys);
}
}

/*//////////////////////////////////////////////////////////////
Expand Down
5 changes: 5 additions & 0 deletions src/interfaces/IExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ interface IExecutor {
*/
function revokeShard(TssKey calldata publicKey) external;

/**
* @dev Revoke a single shard TSS Key.
*/
function revokeShards(TssKey[] calldata publicKey) external;

/**
* @dev List all shards currently registered in the gateway.
*/
Expand Down
46 changes: 33 additions & 13 deletions src/storage/Shards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -196,23 +196,23 @@ library ShardStore {
*/
function register(MainStorage storage store, TssKey calldata newKey) internal returns (bool) {
// Check y-parity
require(newKey.yParity == (newKey.yParity & 3), "y parity bit must be 2 or 3, cannot register shard");
require((newKey.yParity == 2 || newKey.yParity == 3), "y parity bit must be 2 or 3, cannot register shard");

// Read shard from storage
ShardID id = ShardID.wrap(bytes32(newKey.xCoord));
(bool created, ShardInfo storage stored) = getOrAdd(store, id);

// Check if the shard is already registered
if (!created) {
require(stored.nonce == 1 || newKey.yParity == stored.yParity, "tsskey.yParity mismatch");
require(stored.nonce == 1 || newKey.yParity == (stored.yParity | 2), "tsskey.yParity mismatch");
return false;
}

// Get the current status and nonce
ShardInfo memory shard = stored;

require(
shard.createdAtBlock == 0 || shard.yParity == newKey.yParity,
shard.createdAtBlock == 0 || (shard.yParity | 2) == newKey.yParity,
"the provided y-parity doesn't match the existing y-parity, cannot register shard"
);

Expand Down Expand Up @@ -269,7 +269,7 @@ library ShardStore {

if (register(store, key)) {
// Shard registered
created[createdCount++] = TssKey({yParity: key.yParity + 2, xCoord: key.xCoord});
created[createdCount++] = TssKey({yParity: key.yParity, xCoord: key.xCoord});
} else {
// Shard already registered, remove it from the revoke list.
uint256 len = revoked.length;
Expand Down Expand Up @@ -305,34 +305,54 @@ library ShardStore {
* Requirements:
* - The `keys` must be registered.
*/
function revoke(MainStorage storage store, TssKey calldata key) internal {
function revoke(MainStorage storage store, TssKey calldata key) internal returns (bool) {
// Read shard from storage
ShardID id = ShardID.wrap(bytes32(key.xCoord));
ShardInfo memory stored = get(store, id);
(bool exists, ShardInfo memory stored) = tryGet(store, id);

// Check y-parity
require(stored.yParity == (key.yParity & 1), "y parity mismatch, cannot revoke key");
_revoke(store, id);
if (exists) {
// Check y-parity
require(stored.yParity == (key.yParity & 1), "y parity mismatch, cannot revoke key");
return _revoke(store, id);
}
return false;
}

/**
* @dev Revoke Shards keys.
*/
function _revoke(MainStorage storage store, ShardID id) private {
function _revoke(MainStorage storage store, ShardID id) private returns (bool) {
// Remove from the set
store.shards.remove(ShardID.unwrap(id));
StoragePtr ptr = store.shards.remove(ShardID.unwrap(id));
return !ptr.isNull();
}

/**
* @dev Revoke TSS keys im batch.
* Requirements:
* - The `publicKeys` must be registered.
*/
function revokeKeys(MainStorage storage store, TssKey[] calldata publicKeys) internal {
function revokeKeys(MainStorage storage store, TssKey[] calldata publicKeys)
internal
returns (TssKey[] memory revokedKeys)
{
// Revoke tss keys
uint256 keysLength = publicKeys.length;
revokedKeys = new TssKey[](keysLength);
uint256 revokedCount = 0;

for (uint256 i = 0; i < publicKeys.length; i++) {
revoke(store, publicKeys[i]);
if (revoke(store, publicKeys[i])) {
revokedKeys[revokedCount++] = publicKeys[i];
}
}

if (revokedKeys.length != keysLength) {
assembly {
mstore(revokedKeys, revokedCount)
}
}
return revokedKeys;
}

function _t(MainStorage storage store) internal view returns (TssKey[] memory) {}
Expand Down
2 changes: 1 addition & 1 deletion src/utils/GasUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ library GasUtils {
/**
* @dev Base cost of the `IGateway.submitMessage` method.
*/
uint256 internal constant SUBMIT_BASE_COST = 23525 - 22;
uint256 internal constant SUBMIT_BASE_COST = 23525;
Lohann marked this conversation as resolved.
Show resolved Hide resolved

/**
* @dev Extra gas cost of the first `IGateway.submitMessage` method.
Expand Down
2 changes: 1 addition & 1 deletion test/Example.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ contract ExampleTest is Test {
returns (Network[] memory networks)
{
TssKey[] memory keys = new TssKey[](1);
keys[0] = TssKey({yParity: signer.pubkey.yParity() == 28 ? 1 : 0, xCoord: signer.pubkey.px});
keys[0] = TssKey({yParity: signer.pubkey.yParity() == 28 ? 3 : 2, xCoord: signer.pubkey.px});

networks = new Network[](networkIds.length);
for (uint256 i = 0; i < networks.length; i++) {
Expand Down
75 changes: 74 additions & 1 deletion test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

pragma solidity >=0.8.0;

import {Test, console} from "forge-std/Test.sol";
import {Test, console, Vm} from "forge-std/Test.sol";
import {VmSafe} from "forge-std/Vm.sol";
import {TestUtils, SigningKey, SigningUtils} from "./TestUtils.sol";
import {Gateway, GatewayEIP712} from "../src/Gateway.sol";
Expand Down Expand Up @@ -243,6 +243,79 @@ contract GatewayBase is Test {
}
}

function test_shardEvents() external {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat, thanks for writting this test !

TssKey[] memory keys = new TssKey[](10);

// create random shard keys
SigningKey memory signer;
for (uint256 i = 0; i < keys.length; i++) {
signer = TestUtils.signerFromEntropy(bytes32(i));
keys[i] = TssKey({yParity: signer.yParity() == 28 ? 3 : 2, xCoord: signer.xCoord()});
}
_sortTssKeys(keys);

// set shards
vm.prank(ADMIN, ADMIN);
vm.expectEmit(false, false, false, true);
emit IExecutor.ShardsRegistered(keys);
gateway.setShards(keys);

// set a shard which is already registered and verify that is does not emit a event.
vm.prank(ADMIN, ADMIN);
vm.recordLogs();
gateway.setShard(keys[0]);
Vm.Log[] memory entries = vm.getRecordedLogs();
assertEq(entries.length, 0);

// Revoke a registered shard thats not registered.
uint256 unregisteredSignerKey = 11;
signer = TestUtils.signerFromEntropy(bytes32(unregisteredSignerKey));
TssKey memory nonRegisteredKey = TssKey({yParity: signer.yParity() == 28 ? 3 : 2, xCoord: signer.xCoord()});
vm.prank(ADMIN, ADMIN);
vm.recordLogs();
gateway.revokeShard(nonRegisteredKey);
Vm.Log[] memory entries1 = vm.getRecordedLogs();
assertEq(entries1.length, 0);

// Revoke a registered shard
vm.prank(ADMIN, ADMIN);
TssKey[] memory unregisteredShardKey = new TssKey[](1);
unregisteredShardKey[0] = keys[0];
vm.expectEmit(false, false, false, true);
emit IExecutor.ShardsUnregistered(unregisteredShardKey);
gateway.revokeShard(keys[0]);

// Register a revoked shard
vm.prank(ADMIN, ADMIN);
vm.expectEmit(false, false, false, true);
emit IExecutor.ShardsRegistered(unregisteredShardKey);
gateway.setShard(unregisteredShardKey[0]);

// Revoke half of the keys and verify event length
vm.prank(ADMIN, ADMIN);
uint256 halfKeysLength = keys.length / 2;
uint256 secondHalfLength = keys.length - halfKeysLength;
TssKey[] memory firstHalf = new TssKey[](halfKeysLength);
TssKey[] memory secondHalf = new TssKey[](secondHalfLength);
for (uint256 i = 0; i < keys.length; i++){
if (i < halfKeysLength) {
firstHalf[i] = keys[i];
} else {
secondHalf[i - halfKeysLength] = keys[i];
}
}
vm.expectEmit(false, false, false, true);
emit IExecutor.ShardsUnregistered(firstHalf);
gateway.revokeShards(firstHalf);

// register first half keys and check if the other half is unregistered
vm.prank(ADMIN, ADMIN);
vm.expectEmit(false, false, false, true);
emit IExecutor.ShardsRegistered(firstHalf);
emit IExecutor.ShardsUnregistered(secondHalf);
gateway.setShards(firstHalf);
}

function test_Receiver() external {
bytes memory testEncodedCall = abi.encodeCall(
IGmpReceiver.onGmpReceived,
Expand Down
2 changes: 1 addition & 1 deletion test/TestUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ library TestUtils {
require(FACTORY == TestUtils.deployFactory(), "UniversalFactory not deployed");
SigningKey memory signer = TestUtils.createSigner(admin.privateKey);
TssKey[] memory keys = new TssKey[](1);
keys[0] = TssKey({yParity: SigningUtils.yParity(signer) == 28 ? 1 : 0, xCoord: SigningUtils.xCoord(signer)}); // Shard key
keys[0] = TssKey({yParity: SigningUtils.yParity(signer) == 28 ? 3 : 2, xCoord: SigningUtils.xCoord(signer)}); // Shard key
Network[] memory networks = new Network[](2);
address proxyAddr = computeGatewayProxyAddress(admin.addr, salt);
networks[0].id = srcRoute; // sepolia network id
Expand Down
Loading