-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Solidity Coverage - Signature example (#533)
- Adding test coverage for signature example and its tests, - Also fixed a typo on TypeOps tests --------- Signed-off-by: Alfredo Gutierrez <[email protected]> Signed-off-by: Nana Essilfie-Conduah <[email protected]> Co-authored-by: Nana Essilfie-Conduah <[email protected]>
- Loading branch information
1 parent
bb9ec84
commit 3de889b
Showing
4 changed files
with
163 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity >=0.7.0 <0.9.0; | ||
|
||
contract ReceiverPays { | ||
address owner = msg.sender; | ||
|
||
mapping(uint256 => bool) usedNonces; | ||
|
||
constructor() payable {} | ||
|
||
function claimPayment(uint256 amount, uint256 nonce, bytes memory signature) external { | ||
require(!usedNonces[nonce], "invalid nonce"); | ||
usedNonces[nonce] = true; | ||
|
||
// this recreates the message that was signed on the client | ||
bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, amount, nonce, this))); | ||
|
||
require(recoverSigner(message, signature) == owner, "invalid signature"); | ||
|
||
payable(msg.sender).transfer(amount); | ||
} | ||
|
||
/// destroy the contract and reclaim the leftover funds. | ||
function shutdown() external { | ||
require(msg.sender == owner, "only owner can shutdown"); | ||
// This will report a warning due to deprecated selfdestruct | ||
selfdestruct(payable(msg.sender)); | ||
} | ||
|
||
/// signature methods. | ||
function splitSignature(bytes memory sig) | ||
internal | ||
pure | ||
returns (uint8 v, bytes32 r, bytes32 s) | ||
{ | ||
require(sig.length == 65); | ||
|
||
assembly { | ||
// first 32 bytes, after the length prefix. | ||
r := mload(add(sig, 32)) | ||
// second 32 bytes. | ||
s := mload(add(sig, 64)) | ||
// final byte (first byte of the next 32 bytes). | ||
v := byte(0, mload(add(sig, 96))) | ||
} | ||
|
||
return (v, r, s); | ||
} | ||
|
||
function recoverSigner(bytes32 message, bytes memory sig) | ||
internal | ||
pure | ||
returns (address) | ||
{ | ||
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); | ||
|
||
return ecrecover(message, v, r, s); | ||
} | ||
|
||
/// builds a prefixed hash to mimic the behavior of eth_sign. | ||
function prefixed(bytes32 hash) internal pure returns (bytes32) { | ||
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/*- | ||
* | ||
* Hedera Smart Contracts | ||
* | ||
* Copyright (C) 2023 Hedera Hashgraph, LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
const { expect } = require('chai') | ||
const { ethers } = require('hardhat') | ||
const Constants = require('../../constants') | ||
const abi = require('ethereumjs-abi'); | ||
|
||
describe('@solidityequiv4 Signature Example ReceiverPays Test Suite', function () { | ||
let receiverPaysContract, provider, signers, currentNonce, sender, receiver; | ||
|
||
before(async function () { | ||
signers = await ethers.getSigners(); | ||
sender = signers[0]; | ||
receiver = signers[1]; | ||
ethers.provider = sender.provider; | ||
provider = ethers.provider; | ||
const factory = await ethers.getContractFactory(Constants.Path.RECEIVER_PAYS); | ||
const initialFund = ethers.utils.parseEther('4'); | ||
receiverPaysContract = await factory.deploy({ gasLimit: 15000000, value: initialFund }); | ||
currentNonce = 0; | ||
}); | ||
|
||
// claim payment | ||
it('receiver should be able to claim payment and pay for transaction fees', async function () { | ||
const recipientAddress = receiver.address; | ||
const contractBalanceBefore = await signers[0].provider.getBalance(receiverPaysContract.address); | ||
// There is a discrepancy between the amount of decimals for 1 ETH and 1 HBAR. see the tinybar to wei coefficient of 10_000_000_000 | ||
// it should be ethers.utils.parseEther('1'); | ||
const amountToTransfer = 100000000; | ||
|
||
// Generate signature for payment | ||
const signedPayment = await signPayment(recipientAddress, amountToTransfer, currentNonce, receiverPaysContract.address); | ||
|
||
// Claim payment | ||
const contract = receiverPaysContract.connect(receiver); | ||
await contract.claimPayment(amountToTransfer, currentNonce, signedPayment); | ||
|
||
// Verify payment is received | ||
const contractBalanceAfter = await signers[0].provider.getBalance(receiverPaysContract.address); | ||
|
||
expect(contractBalanceAfter).to.equal(contractBalanceBefore.sub(ethers.utils.parseEther('1'))); | ||
|
||
currentNonce++; | ||
}); | ||
|
||
// try to shutdown contract as receiver | ||
it('receiver should not be able to shutdown contract', async function () { | ||
const contract = receiverPaysContract.connect(receiver); | ||
let errorOccurred = false; | ||
try { | ||
const tx = await contract.shutdown(); | ||
await tx.wait(); | ||
} catch (error) { | ||
expect(error.reason).to.be.equal("transaction failed"); | ||
errorOccurred = true; | ||
} | ||
expect(errorOccurred).to.be.true; | ||
// verify the contract still has balance | ||
const contractBalance = await signers[0].provider.getBalance(receiverPaysContract.address); | ||
expect(contractBalance).to.be.gt(0); | ||
}); | ||
|
||
// should be able to shutdown as sender | ||
it('sender should be able to shutdown contract', async function () { | ||
const contract = receiverPaysContract.connect(sender); | ||
await contract.shutdown(); | ||
// verify contract is shutdown, contract should have no balance left | ||
const contractBalance = await signers[0].provider.getBalance(receiverPaysContract.address); | ||
expect(contractBalance).to.be.equal(0); | ||
}); | ||
|
||
|
||
async function signPayment(recipient, amount, nonce, contractAddress) { | ||
const hash = ethers.utils.solidityKeccak256(["address", "uint256", "uint256", "address"],[recipient, amount, nonce, contractAddress]) | ||
// Sign the hash | ||
const signature = await sender.signMessage(ethers.utils.arrayify(hash)); | ||
return signature; | ||
} | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters