Commit eb454ac7 authored by Tarun Khasnavis's avatar Tarun Khasnavis Committed by GitHub

Update Faucet contract to use bridging on drip function (#10621)

* adding bridging to the faucet drip call

* updating the logic based on suggesstions

* adding changeset

* fixing ci issue

* update auth module tests

* final fix

* lint fix

* update snapshots

* update changeset

* change to patch

---------
Co-authored-by: default avatarTarun Khasnavis <tarunkhasnavis@Taruns-MacBook-Pro.local>
Co-authored-by: default avatartre <tremarkley@gmail.com>
parent 1c22d5f2
---
'@eth-optimism/contracts-bedrock': patch
---
Add data field to faucet contract drip parameters
...@@ -55,10 +55,20 @@ ...@@ -55,10 +55,20 @@
"name": "recipient", "name": "recipient",
"type": "address" "type": "address"
}, },
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
},
{ {
"internalType": "bytes32", "internalType": "bytes32",
"name": "nonce", "name": "nonce",
"type": "bytes32" "type": "bytes32"
},
{
"internalType": "uint32",
"name": "gasLimit",
"type": "uint32"
} }
], ],
"internalType": "struct Faucet.DripParameters", "internalType": "struct Faucet.DripParameters",
......
...@@ -76,10 +76,20 @@ ...@@ -76,10 +76,20 @@
"name": "recipient", "name": "recipient",
"type": "address" "type": "address"
}, },
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
},
{ {
"internalType": "bytes32", "internalType": "bytes32",
"name": "nonce", "name": "nonce",
"type": "bytes32" "type": "bytes32"
},
{
"internalType": "uint32",
"name": "gasLimit",
"type": "uint32"
} }
], ],
"internalType": "struct Faucet.DripParameters", "internalType": "struct Faucet.DripParameters",
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { IFaucetAuthModule } from "./authmodules/IFaucetAuthModule.sol"; import { IFaucetAuthModule } from "./authmodules/IFaucetAuthModule.sol";
import { SafeCall } from "../../libraries/SafeCall.sol";
/// @title SafeSend /// @title SafeSend
/// @notice Sends ETH to a recipient account without triggering any code. /// @notice Sends ETH to a recipient account without triggering any code.
...@@ -25,7 +26,9 @@ contract Faucet { ...@@ -25,7 +26,9 @@ contract Faucet {
/// @notice Parameters for a drip. /// @notice Parameters for a drip.
struct DripParameters { struct DripParameters {
address payable recipient; address payable recipient;
bytes data;
bytes32 nonce; bytes32 nonce;
uint32 gasLimit;
} }
/// @notice Parameters for authentication. /// @notice Parameters for authentication.
...@@ -113,14 +116,17 @@ contract Faucet { ...@@ -113,14 +116,17 @@ contract Faucet {
"Faucet: drip parameters could not be verified by security module" "Faucet: drip parameters could not be verified by security module"
); );
// Verify recepient is not the faucet address.
require(_params.recipient != address(this), "Faucet: cannot drip to itself");
// Set the next timestamp at which this auth id can be used. // Set the next timestamp at which this auth id can be used.
timeouts[_auth.module][_auth.id] = block.timestamp + config.ttl; timeouts[_auth.module][_auth.id] = block.timestamp + config.ttl;
// Mark the nonce as used. // Mark the nonce as used.
nonces[_auth.id][_params.nonce] = true; nonces[_auth.id][_params.nonce] = true;
// Execute a safe transfer of ETH to the recipient account. // Execute transfer of ETH to the recipient account.
new SafeSend{ value: config.amount }(_params.recipient); SafeCall.call(_params.recipient, _params.gasLimit, config.amount, _params.data);
emit Drip(config.name, _auth.id, config.amount, _params.recipient); emit Drip(config.name, _auth.id, config.amount, _params.recipient);
} }
......
...@@ -106,6 +106,8 @@ contract FaucetTest is Faucet_Initializer { ...@@ -106,6 +106,8 @@ contract FaucetTest is Faucet_Initializer {
function test_authAdmin_drip_succeeds() external { function test_authAdmin_drip_succeeds() external {
_enableFaucetAuthModules(); _enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
bytes memory signature = issueProofWithEIP712Domain( bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey, faucetAuthAdminKey,
bytes(optimistNftFamName), bytes(optimistNftFamName),
...@@ -119,7 +121,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -119,7 +121,7 @@ contract FaucetTest is Faucet_Initializer {
vm.prank(nonAdmin); vm.prank(nonAdmin);
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce), Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature) Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
); );
} }
...@@ -127,6 +129,8 @@ contract FaucetTest is Faucet_Initializer { ...@@ -127,6 +129,8 @@ contract FaucetTest is Faucet_Initializer {
function test_nonAdmin_drip_fails() external { function test_nonAdmin_drip_fails() external {
_enableFaucetAuthModules(); _enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
bytes memory signature = issueProofWithEIP712Domain( bytes memory signature = issueProofWithEIP712Domain(
nonAdminKey, nonAdminKey,
bytes(optimistNftFamName), bytes(optimistNftFamName),
...@@ -141,7 +145,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -141,7 +145,7 @@ contract FaucetTest is Faucet_Initializer {
vm.prank(nonAdmin); vm.prank(nonAdmin);
vm.expectRevert("Faucet: drip parameters could not be verified by security module"); vm.expectRevert("Faucet: drip parameters could not be verified by security module");
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce), Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature) Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
); );
} }
...@@ -149,6 +153,8 @@ contract FaucetTest is Faucet_Initializer { ...@@ -149,6 +153,8 @@ contract FaucetTest is Faucet_Initializer {
function test_drip_optimistNftSendsCorrectAmount_succeeds() external { function test_drip_optimistNftSendsCorrectAmount_succeeds() external {
_enableFaucetAuthModules(); _enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
bytes memory signature = issueProofWithEIP712Domain( bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey, faucetAuthAdminKey,
bytes(optimistNftFamName), bytes(optimistNftFamName),
...@@ -163,7 +169,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -163,7 +169,7 @@ contract FaucetTest is Faucet_Initializer {
uint256 recipientBalanceBefore = address(fundsReceiver).balance; uint256 recipientBalanceBefore = address(fundsReceiver).balance;
vm.prank(nonAdmin); vm.prank(nonAdmin);
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce), Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature) Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
); );
uint256 recipientBalanceAfter = address(fundsReceiver).balance; uint256 recipientBalanceAfter = address(fundsReceiver).balance;
...@@ -173,6 +179,8 @@ contract FaucetTest is Faucet_Initializer { ...@@ -173,6 +179,8 @@ contract FaucetTest is Faucet_Initializer {
function test_drip_githubSendsCorrectAmount_succeeds() external { function test_drip_githubSendsCorrectAmount_succeeds() external {
_enableFaucetAuthModules(); _enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
bytes memory signature = issueProofWithEIP712Domain( bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey, faucetAuthAdminKey,
bytes(githubFamName), bytes(githubFamName),
...@@ -187,7 +195,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -187,7 +195,7 @@ contract FaucetTest is Faucet_Initializer {
uint256 recipientBalanceBefore = address(fundsReceiver).balance; uint256 recipientBalanceBefore = address(fundsReceiver).balance;
vm.prank(nonAdmin); vm.prank(nonAdmin);
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce), Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
); );
uint256 recipientBalanceAfter = address(fundsReceiver).balance; uint256 recipientBalanceAfter = address(fundsReceiver).balance;
...@@ -197,6 +205,8 @@ contract FaucetTest is Faucet_Initializer { ...@@ -197,6 +205,8 @@ contract FaucetTest is Faucet_Initializer {
function test_drip_emitsEvent_succeeds() external { function test_drip_emitsEvent_succeeds() external {
_enableFaucetAuthModules(); _enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
bytes memory signature = issueProofWithEIP712Domain( bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey, faucetAuthAdminKey,
bytes(githubFamName), bytes(githubFamName),
...@@ -213,7 +223,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -213,7 +223,7 @@ contract FaucetTest is Faucet_Initializer {
vm.prank(nonAdmin); vm.prank(nonAdmin);
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce), Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
); );
} }
...@@ -221,6 +231,8 @@ contract FaucetTest is Faucet_Initializer { ...@@ -221,6 +231,8 @@ contract FaucetTest is Faucet_Initializer {
function test_drip_disabledModule_reverts() external { function test_drip_disabledModule_reverts() external {
_enableFaucetAuthModules(); _enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
bytes memory signature = issueProofWithEIP712Domain( bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey, faucetAuthAdminKey,
bytes(githubFamName), bytes(githubFamName),
...@@ -234,7 +246,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -234,7 +246,7 @@ contract FaucetTest is Faucet_Initializer {
vm.startPrank(faucetContractAdmin); vm.startPrank(faucetContractAdmin);
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce), Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
); );
...@@ -242,7 +254,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -242,7 +254,7 @@ contract FaucetTest is Faucet_Initializer {
vm.expectRevert("Faucet: provided auth module is not supported by this faucet"); vm.expectRevert("Faucet: provided auth module is not supported by this faucet");
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce), Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
); );
vm.stopPrank(); vm.stopPrank();
...@@ -251,6 +263,8 @@ contract FaucetTest is Faucet_Initializer { ...@@ -251,6 +263,8 @@ contract FaucetTest is Faucet_Initializer {
function test_drip_preventsReplayAttacks_succeeds() external { function test_drip_preventsReplayAttacks_succeeds() external {
_enableFaucetAuthModules(); _enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
bytes memory signature = issueProofWithEIP712Domain( bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey, faucetAuthAdminKey,
bytes(githubFamName), bytes(githubFamName),
...@@ -264,13 +278,13 @@ contract FaucetTest is Faucet_Initializer { ...@@ -264,13 +278,13 @@ contract FaucetTest is Faucet_Initializer {
vm.startPrank(faucetContractAdmin); vm.startPrank(faucetContractAdmin);
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce), Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
); );
vm.expectRevert("Faucet: nonce has already been used"); vm.expectRevert("Faucet: nonce has already been used");
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce), Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
); );
vm.stopPrank(); vm.stopPrank();
...@@ -279,6 +293,8 @@ contract FaucetTest is Faucet_Initializer { ...@@ -279,6 +293,8 @@ contract FaucetTest is Faucet_Initializer {
function test_drip_beforeTimeout_reverts() external { function test_drip_beforeTimeout_reverts() external {
_enableFaucetAuthModules(); _enableFaucetAuthModules();
bytes32 nonce0 = faucetHelper.consumeNonce(); bytes32 nonce0 = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
bytes memory signature0 = issueProofWithEIP712Domain( bytes memory signature0 = issueProofWithEIP712Domain(
faucetAuthAdminKey, faucetAuthAdminKey,
bytes(githubFamName), bytes(githubFamName),
...@@ -292,7 +308,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -292,7 +308,7 @@ contract FaucetTest is Faucet_Initializer {
vm.startPrank(faucetContractAdmin); vm.startPrank(faucetContractAdmin);
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce0), Faucet.DripParameters(payable(fundsReceiver), data, nonce0, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature0) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature0)
); );
...@@ -310,7 +326,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -310,7 +326,7 @@ contract FaucetTest is Faucet_Initializer {
vm.expectRevert("Faucet: auth cannot be used yet because timeout has not elapsed"); vm.expectRevert("Faucet: auth cannot be used yet because timeout has not elapsed");
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce1), Faucet.DripParameters(payable(fundsReceiver), data, nonce1, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature1) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature1)
); );
vm.stopPrank(); vm.stopPrank();
...@@ -319,6 +335,8 @@ contract FaucetTest is Faucet_Initializer { ...@@ -319,6 +335,8 @@ contract FaucetTest is Faucet_Initializer {
function test_drip_afterTimeout_succeeds() external { function test_drip_afterTimeout_succeeds() external {
_enableFaucetAuthModules(); _enableFaucetAuthModules();
bytes32 nonce0 = faucetHelper.consumeNonce(); bytes32 nonce0 = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
bytes memory signature0 = issueProofWithEIP712Domain( bytes memory signature0 = issueProofWithEIP712Domain(
faucetAuthAdminKey, faucetAuthAdminKey,
bytes(githubFamName), bytes(githubFamName),
...@@ -332,7 +350,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -332,7 +350,7 @@ contract FaucetTest is Faucet_Initializer {
vm.startPrank(faucetContractAdmin); vm.startPrank(faucetContractAdmin);
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce0), Faucet.DripParameters(payable(fundsReceiver), data, nonce0, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature0) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature0)
); );
...@@ -350,7 +368,7 @@ contract FaucetTest is Faucet_Initializer { ...@@ -350,7 +368,7 @@ contract FaucetTest is Faucet_Initializer {
vm.warp(startingTimestamp + 1 days + 1 seconds); vm.warp(startingTimestamp + 1 days + 1 seconds);
faucet.drip( faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce1), Faucet.DripParameters(payable(fundsReceiver), data, nonce1, gasLimit),
Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature1) Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature1)
); );
vm.stopPrank(); vm.stopPrank();
......
...@@ -81,6 +81,8 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -81,6 +81,8 @@ contract AdminFaucetAuthModuleTest is Test {
/// @notice Assert that verify returns true for valid proofs signed by admins. /// @notice Assert that verify returns true for valid proofs signed by admins.
function test_adminProof_verify_succeeds() external { function test_adminProof_verify_succeeds() external {
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
address fundsReceiver = makeAddr("fundsReceiver"); address fundsReceiver = makeAddr("fundsReceiver");
bytes memory proof = issueProofWithEIP712Domain( bytes memory proof = issueProofWithEIP712Domain(
adminKey, adminKey,
...@@ -96,7 +98,9 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -96,7 +98,9 @@ contract AdminFaucetAuthModuleTest is Test {
vm.prank(nonAdmin); vm.prank(nonAdmin);
assertEq( assertEq(
adminFam.verify( adminFam.verify(
Faucet.DripParameters(payable(fundsReceiver), nonce), keccak256(abi.encodePacked(fundsReceiver)), proof Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
keccak256(abi.encodePacked(fundsReceiver)),
proof
), ),
true true
); );
...@@ -105,6 +109,8 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -105,6 +109,8 @@ contract AdminFaucetAuthModuleTest is Test {
/// @notice Assert that verify returns false for proofs signed by nonadmins. /// @notice Assert that verify returns false for proofs signed by nonadmins.
function test_nonAdminProof_verify_succeeds() external { function test_nonAdminProof_verify_succeeds() external {
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
address fundsReceiver = makeAddr("fundsReceiver"); address fundsReceiver = makeAddr("fundsReceiver");
bytes memory proof = issueProofWithEIP712Domain( bytes memory proof = issueProofWithEIP712Domain(
nonAdminKey, nonAdminKey,
...@@ -120,7 +126,9 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -120,7 +126,9 @@ contract AdminFaucetAuthModuleTest is Test {
vm.prank(admin); vm.prank(admin);
assertEq( assertEq(
adminFam.verify( adminFam.verify(
Faucet.DripParameters(payable(fundsReceiver), nonce), keccak256(abi.encodePacked(fundsReceiver)), proof Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
keccak256(abi.encodePacked(fundsReceiver)),
proof
), ),
false false
); );
...@@ -130,6 +138,8 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -130,6 +138,8 @@ contract AdminFaucetAuthModuleTest is Test {
/// than the id in the call to verify. /// than the id in the call to verify.
function test_proofWithWrongId_verify_succeeds() external { function test_proofWithWrongId_verify_succeeds() external {
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
bytes memory data = "0x";
uint32 gasLimit = 200000;
address fundsReceiver = makeAddr("fundsReceiver"); address fundsReceiver = makeAddr("fundsReceiver");
address randomAddress = makeAddr("randomAddress"); address randomAddress = makeAddr("randomAddress");
bytes memory proof = issueProofWithEIP712Domain( bytes memory proof = issueProofWithEIP712Domain(
...@@ -146,7 +156,9 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -146,7 +156,9 @@ contract AdminFaucetAuthModuleTest is Test {
vm.prank(admin); vm.prank(admin);
assertEq( assertEq(
adminFam.verify( adminFam.verify(
Faucet.DripParameters(payable(fundsReceiver), nonce), keccak256(abi.encodePacked(randomAddress)), proof Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit),
keccak256(abi.encodePacked(randomAddress)),
proof
), ),
false false
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment