Skip to content

Commit

Permalink
feat: added ExchangeRateMock smart contract (#414)
Browse files Browse the repository at this point in the history
* feat: added ExchangeRateMock contract and a suite of unit test

Signed-off-by: Logan Nguyen <[email protected]>

* dapp-feat: integrated ExchangeRate APIs to DApp Playground

Signed-off-by: Logan Nguyen <[email protected]>

---------

Signed-off-by: Logan Nguyen <[email protected]>
  • Loading branch information
quiet-node authored Sep 20, 2023
1 parent 1f14bea commit 568fdc7
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "ExchangeRateMock",
"sourceName": "contracts/exchange-rate-precompile/ExchangeRateMock.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "tinybars",
"type": "uint256"
}
],
"name": "TinyBars",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "tinycents",
"type": "uint256"
}
],
"name": "TinyCents",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tinybars",
"type": "uint256"
}
],
"name": "convertTinybarsToTinycents",
"outputs": [
{
"internalType": "uint256",
"name": "tineycents",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tineycents",
"type": "uint256"
}
],
"name": "convertTinycentsToTinybars",
"outputs": [
{
"internalType": "uint256",
"name": "tinybars",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x608060405234801561000f575f80fd5b506102c88061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80634abd27a314610038578063677feb671461005d575b5f80fd5b61004b610046366004610238565b610070565b60405190815260200160405180910390f35b61004b61006b366004610238565b6100ba565b5f61007a826100f7565b90507f1e2eedf37f019c356cd1ab2a7445bef30fa8b9e32be6636e6efae70244c7b3e0816040516100ad91815260200190565b60405180910390a1919050565b5f6100c482610201565b90507fc399bb30ec076dee6179663a550714898c27a4a8cf23a336b2e49ec777779459816040516100ad91815260200190565b5f805f61016873ffffffffffffffffffffffffffffffffffffffff16632e3cff6a60e01b8560405160240161012e91815260200190565b60408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051610199919061024f565b5f604051808303815f865af19150503d805f81146101d2576040519150601f19603f3d011682016040523d82523d5f602084013e6101d7565b606091505b5091509150816101e5575f80fd5b808060200190518101906101f9919061027b565b949350505050565b5f805f61016873ffffffffffffffffffffffffffffffffffffffff166343a8822960e01b8560405160240161012e91815260200190565b5f60208284031215610248575f80fd5b5035919050565b5f82515f5b8181101561026e5760208186018101518583015201610254565b505f920191825250919050565b5f6020828403121561028b575f80fd5b505191905056fea2646970667358221220b008a9e9abd5aa431fa5c89bb82f148bd9b6b8abbb0011f427a5434f960895a664736f6c63430008140033",
"deployedBytecode": "0x608060405234801561000f575f80fd5b5060043610610034575f3560e01c80634abd27a314610038578063677feb671461005d575b5f80fd5b61004b610046366004610238565b610070565b60405190815260200160405180910390f35b61004b61006b366004610238565b6100ba565b5f61007a826100f7565b90507f1e2eedf37f019c356cd1ab2a7445bef30fa8b9e32be6636e6efae70244c7b3e0816040516100ad91815260200190565b60405180910390a1919050565b5f6100c482610201565b90507fc399bb30ec076dee6179663a550714898c27a4a8cf23a336b2e49ec777779459816040516100ad91815260200190565b5f805f61016873ffffffffffffffffffffffffffffffffffffffff16632e3cff6a60e01b8560405160240161012e91815260200190565b60408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051610199919061024f565b5f604051808303815f865af19150503d805f81146101d2576040519150601f19603f3d011682016040523d82523d5f602084013e6101d7565b606091505b5091509150816101e5575f80fd5b808060200190518101906101f9919061027b565b949350505050565b5f805f61016873ffffffffffffffffffffffffffffffffffffffff166343a8822960e01b8560405160240161012e91815260200190565b5f60208284031215610248575f80fd5b5035919050565b5f82515f5b8181101561026e5760208186018101518583015201610254565b505f920191825250919050565b5f6020828403121561028b575f80fd5b505191905056fea2646970667358221220b008a9e9abd5aa431fa5c89bb82f148bd9b6b8abbb0011f427a5434f960895a664736f6c63430008140033",
"linkReferences": {},
"deployedLinkReferences": {}
}
19 changes: 19 additions & 0 deletions contracts/exchange-rate-precompile/ExchangeRateMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.5.0 <0.9.0;

import "./SelfFunding.sol";

contract ExchangeRateMock is SelfFunding {
event TinyBars(uint256 tinybars);
event TinyCents(uint256 tinycents);

function convertTinycentsToTinybars(uint256 tineycents) external returns (uint256 tinybars) {
tinybars = tinycentsToTinybars(tineycents);
emit TinyBars(tinybars);
}

function convertTinybarsToTinycents(uint256 tinybars) external returns (uint256 tineycents) {
tineycents = tinybarsToTinycents(tinybars);
emit TinyCents(tineycents);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*-
*
* 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.
*
*/

import { Contract } from 'ethers';
import { MOCK_GAS_LIMIT, MOCK_TX_HASH } from '../../utils/common/constants';
import { handleExchangeRate } from '@/api/hedera/exchange-rate-interactions';

describe('Exchange Rate Test Suite', () => {
const amount = 100000000;
const mockConvertedAmount = 833333;

// mock resolved return value
const contractMockedResolvedValue = (eventName: string) => {
return {
wait: jest.fn().mockResolvedValue({
logs: [
{
fragment: {
name: eventName,
},
data: mockConvertedAmount,
},
],
hash: MOCK_TX_HASH,
}),
};
};

// mock baseContract object
const baseContract = {
convertTinycentsToTinybars: jest
.fn()
.mockResolvedValue(contractMockedResolvedValue('TinyBars')),
convertTinybarsToTinycents: jest
.fn()
.mockResolvedValue(contractMockedResolvedValue('TinyCents')),
};

it('should execute handleExchangeRate with API === "CENT_TO_BAR" and return a txHash and convertedAmount', async () => {
const txRes = await handleExchangeRate(
baseContract as unknown as Contract,
'CENT_TO_BAR',
amount,
MOCK_GAS_LIMIT
);

expect(txRes.err).toBeNull;
expect(txRes.transactionHash).toBe(MOCK_TX_HASH);
expect(txRes.convertedAmount).toBe(mockConvertedAmount);
});

it('should execute handleExchangeRate with API === "BAR_TO_CENT" and return a txHash and convertedAmount', async () => {
const txRes = await handleExchangeRate(
baseContract as unknown as Contract,
'BAR_TO_CENT',
amount,
MOCK_GAS_LIMIT
);

expect(txRes.err).toBeNull;
expect(txRes.transactionHash).toBe(MOCK_TX_HASH);
expect(txRes.convertedAmount).toBe(mockConvertedAmount);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*-
*
* 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.
*
*/

import { Contract } from 'ethers';

/**
* @dev handle converting tinycents to tinybars and vice versa
*
* @dev integrates exchangeRate.convertTinycentsToTinybars()
*
* @dev integrates exchangeRate.convertTinybarsToTinycents()
*
* @param baseContract: ethers.Contract
*
* @param API: "CENT_TO_BAR" | "BAR_TO_CENT"
*
* @param amount: number
*
* @param gasLimit: number
*
* @return Promise<ExchangeRateContractResult>
*/
export const handleExchangeRate = async (
baseContract: Contract,
API: 'CENT_TO_BAR' | 'BAR_TO_CENT',
amount: number,
gasLimit: number
): Promise<ExchangeRateContractResult> => {
// sanitize param
if (amount < 0) {
console.error('Amount to convert cannot be negative');
return { err: 'Amount to convert cannot be negative' };
}

// Event name map
const eventNameMap = {
CENT_TO_BAR: 'TinyBars',
BAR_TO_CENT: 'TinyCents',
};

try {
// invoke contract method
let tx;
if (API === 'CENT_TO_BAR') {
tx = await baseContract.convertTinycentsToTinybars(amount, { gasLimit });
} else {
tx = await baseContract.convertTinybarsToTinycents(amount, { gasLimit });
}

// retrieve txReceipt
const txReceipt = await tx.wait();

const { data } = txReceipt.logs.filter((event: any) => event.fragment.name === eventNameMap[API])[0];

return { transactionHash: txReceipt.hash, convertedAmount: data };
} catch (err: any) {
console.error(err);
return { err, transactionHash: err.receipt && err.receipt.hash };
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*-
*
* 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.
*
*/

/** @dev an interface for the results returned back from interacting with PRNG contract */
interface ExchangeRateContractResult {
convertedAmount?: number;
transactionHash?: string;
err?: any;
}
3 changes: 3 additions & 0 deletions test/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const Events = {
MintedNft: 'MintedNft',
GetFungibleTokenInfo: 'GetFungibleTokenInfo',
GetNonFungibleTokenInfo: 'GetNonFungibleTokenInfo',
TinyBars: 'TinyBars',
TinyCents: 'TinyCents',
}

const Path = {
Expand Down Expand Up @@ -94,6 +96,7 @@ const Contract = {
ERC20PausableMock: 'ERC20PausableMock',
ERC20SnapshotMock: 'ERC20SnapshotMock',
HRCContract: 'HRCContract',
ExchangeRateMock: 'ExchangeRateMock',
}

const CALL_EXCEPTION = 'CALL_EXCEPTION'
Expand Down
64 changes: 64 additions & 0 deletions test/exchange-rate-precompile/ExchangeRateMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*-
*
* 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')

describe('ExchangeRateMock tests', function () {
let exchangeRateMock
const gasLimit = 1000000
const tinybars = 100000000
const tinycents = 100000000

before(async function () {
const factory = await ethers.getContractFactory(
Constants.Contract.ExchangeRateMock
)

exchangeRateMock = await factory.deploy()
})

it('should be able to execute convertTinycentsToTinybars', async function () {
const tx = await exchangeRateMock.convertTinycentsToTinybars(tinycents, {
gasLimit,
})

const txReceipt = await tx.wait()
const result = txReceipt.events.filter(
(e) => e.event === Constants.Events.TinyBars
)[0].args[0]

expect(result).to.exist
})

it('should be able to execute convertTinybarsToTinycents', async function () {
const tx = await exchangeRateMock.convertTinybarsToTinycents(tinybars, {
gasLimit,
})

const txReceipt = await tx.wait()
const result = txReceipt.events.filter(
(e) => e.event === Constants.Events.TinyCents
)[0].args[0]

expect(result).to.exist
})
})

0 comments on commit 568fdc7

Please sign in to comment.