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

Implement OdisPayments.sol and unit tests #9740

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"generate:shrinkwrap": "npm install --production && npm shrinkwrap",
"check:shrinkwrap": "npm install --production && npm shrinkwrap && ./scripts/check_shrinkwrap_dirty.sh",
"prepack": "yarn run build && oclif-dev manifest && oclif-dev readme && yarn run check:shrinkwrap",
"test:reset": "yarn --cwd ../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../dev-utils/src/migration-override.json --upto 26 --release_gold_contracts scripts/truffle/releaseGoldExampleConfigs.json",
"test:reset": "yarn --cwd ../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../dev-utils/src/migration-override.json --upto 27 --release_gold_contracts scripts/truffle/releaseGoldExampleConfigs.json",
"test:livechain": "yarn --cwd ../protocol devchain run-tar .tmp/devchain.tar.gz",
"test": "TZ=UTC jest --runInBand"
},
Expand Down
75 changes: 75 additions & 0 deletions packages/protocol/contracts/identity/OdisPayments.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
pragma solidity ^0.5.13;

import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";

import "./interfaces/IOdisPayments.sol";
import "../common/interfaces/ICeloVersionedContract.sol";

import "../common/Initializable.sol";
import "../common/UsingRegistryV2.sol";
import "../common/libraries/ReentrancyGuard.sol";

/**
* @title Stores balance to be used for ODIS quota calculation.
*/
contract OdisPayments is
IOdisPayments,
ICeloVersionedContract,
ReentrancyGuard,
Ownable,
Initializable,
UsingRegistryV2
{
using SafeMath for uint256;
using SafeERC20 for IERC20;

event PaymentMade(address indexed account, uint256 valueInCUSD);

// Store amount sent (all time) from account to this contract.
// Values in totalPaidCUSD should only ever be incremented, since ODIS relies
// on all-time paid balance to compute every quota.
mapping(address => uint256) public totalPaidCUSD;

/**
* @notice Sets initialized == true on implementation contracts.
* @param test Set to true to skip implementation initialization.
*/
constructor(bool test) public Initializable(test) {}

/**
* @notice Returns the storage, major, minor, and patch version of the contract.
* @return Storage version of the contract.
* @return Major version of the contract.
* @return Minor version of the contract.
* @return Patch version of the contract.
*/
function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) {
return (1, 1, 0, 0);
}

/**
* @notice Used in place of the constructor to allow the contract to be upgradable via proxy.
*/
function initialize() external initializer {
_transferOwnership(msg.sender);
}

/**
* @notice Sends cUSD to this contract to pay for ODIS quota (for queries).
* @param account The account whose balance to increment.
* @param value The amount in cUSD to pay.
* @dev Throws if cUSD transfer fails.
*/
function payInCUSD(address account, uint256 value) external nonReentrant {
IERC20(registryContract.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID)).safeTransferFrom(
msg.sender,
address(this),
value
);
totalPaidCUSD[account] = totalPaidCUSD[account].add(value);
emit PaymentMade(account, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pragma solidity ^0.5.13;

interface IOdisPayments {
function payInCUSD(address account, uint256 value) external;
function totalPaidCUSD(address) external view returns (uint256);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pragma solidity ^0.5.13;

import "../../common/Proxy.sol";

/* solhint-disable no-empty-blocks */
contract OdisPaymentsProxy is Proxy {}
3 changes: 3 additions & 0 deletions packages/protocol/governanceConstitution.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ const DefaultConstitution = {
addSlasher: 0.9,
removeSlasher: 0.8,
},
OdisPayments: {
default: 0.6,
},
// Values for all proxied contracts.
proxy: {
_transferOwnership: 0.9,
Expand Down
2 changes: 2 additions & 0 deletions packages/protocol/lib/registry-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export enum CeloContractName {
GovernanceApproverMultiSig = 'GovernanceApproverMultiSig',
GrandaMento = 'GrandaMento',
LockedGold = 'LockedGold',
OdisPayments = 'OdisPayments',
Random = 'Random',
Reserve = 'Reserve',
ReserveSpenderMultiSig = 'ReserveSpenderMultiSig',
Expand Down Expand Up @@ -62,6 +63,7 @@ export const hasEntryInRegistry: string[] = [
CeloContractName.GoldToken,
CeloContractName.GovernanceSlasher,
CeloContractName.GrandaMento,
CeloContractName.OdisPayments,
CeloContractName.Random,
CeloContractName.Reserve,
CeloContractName.SortedOracles,
Expand Down
14 changes: 14 additions & 0 deletions packages/protocol/migrations/26_odispayments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils'
import { OdisPaymentsInstance } from 'types'

const initializeArgs = async () => {
return []
}

module.exports = deploymentForCoreContract<OdisPaymentsInstance>(
web3,
artifacts,
CeloContractName.OdisPayments,
initializeArgs
)
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ module.exports = deploymentForCoreContract<GovernanceInstance>(
'GovernanceSlasher',
'GrandaMento',
'LockedGold',
'OdisPayments',
'Random',
'Registry',
'Reserve',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"FederatedAttestations": []
"FederatedAttestations": [],
"OdisPayments": []
}
5 changes: 3 additions & 2 deletions packages/protocol/scripts/bash/backupmigrations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ else
# cp migrations.bak/23_governance_approver_multisig.* migrations/
# cp migrations.bak/24_grandamento.* migrations/
# cp migrations.bak/25_federated_attestations.* migrations/
# cp migrations.bak/26_governance.* migrations/
# cp migrations.bak/27_elect_validators.* migrations/
# cp migrations.bak/26_odispayments.* migrations/
# cp migrations.bak/27_governance.* migrations/
# cp migrations.bak/28_elect_validators.* migrations/
fi
2 changes: 2 additions & 0 deletions packages/protocol/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const ProxyContracts = [
'LockedGoldProxy',
'MetaTransactionWalletProxy',
'MetaTransactionWalletDeployerProxy',
'OdisPaymentsProxy',
'RegistryProxy',
'ReserveProxy',
'ReserveSpenderMultiSigProxy',
Expand Down Expand Up @@ -69,6 +70,7 @@ export const CoreContracts = [
'Escrow',
'FederatedAttestations',
'Random',
'OdisPayments',

// stability
'Exchange',
Expand Down
155 changes: 155 additions & 0 deletions packages/protocol/test/identity/odispayments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import {
assertEqualBN,
assertLogMatches2,
assertRevert,
assumeOwnership,
} from '@celo/protocol/lib/test-utils'
import { getDeployedProxiedContract } from '@celo/protocol/lib/web3-utils'
import { fixed1 } from '@celo/utils/src/fixidity'
import {
FreezerContract,
FreezerInstance,
OdisPaymentsContract,
OdisPaymentsInstance,
RegistryInstance,
StableTokenContract,
StableTokenInstance,
} from 'types'

const Freezer: FreezerContract = artifacts.require('Freezer')
const OdisPayments: OdisPaymentsContract = artifacts.require('OdisPayments')
const StableTokenCUSD: StableTokenContract = artifacts.require('StableToken')

const SECONDS_IN_A_DAY = 60 * 60 * 24

contract('OdisPayments', (accounts: string[]) => {
let freezer: FreezerInstance
let odisPayments: OdisPaymentsInstance
let registry: RegistryInstance
let stableTokenCUSD: StableTokenInstance

const owner = accounts[0]
const sender = accounts[1]
const startingBalanceCUSD = 1000

before(async () => {
// Mocking Registry.sol when using UsingRegistryV2.sol
registry = await getDeployedProxiedContract('Registry', artifacts)
eelanagaraj marked this conversation as resolved.
Show resolved Hide resolved
if ((await registry.owner()) !== owner) {
// In CI we need to assume ownership, locally using quicktest we don't
eelanagaraj marked this conversation as resolved.
Show resolved Hide resolved
await assumeOwnership(['Registry'], owner)
}
})

beforeEach(async () => {
odisPayments = await OdisPayments.new(true, { from: owner })
await odisPayments.initialize()

stableTokenCUSD = await StableTokenCUSD.new(true, { from: owner })
await registry.setAddressFor(CeloContractName.StableToken, stableTokenCUSD.address)
await stableTokenCUSD.initialize(
'Celo Dollar',
'cUSD',
18,
registry.address,
fixed1,
SECONDS_IN_A_DAY,
// Initialize owner and sender with balances
[owner, sender],
[startingBalanceCUSD, startingBalanceCUSD],
'Exchange' // USD
)

// StableToken is freezable so this is necessary for transferFrom calls
freezer = await Freezer.new(true, { from: owner })
await freezer.initialize()
await registry.setAddressFor(CeloContractName.Freezer, freezer.address)
})

describe('#initialize()', () => {
it('should have set the owner', async () => {
const actualOwner: string = await odisPayments.owner()
assert.equal(actualOwner, owner)
})

it('should not be callable again', async () => {
await assertRevert(odisPayments.initialize())
})
})

describe('#payInCUSD', () => {
eelanagaraj marked this conversation as resolved.
Show resolved Hide resolved
const checkStateCUSD = async (
cusdSender: string,
odisPaymentReceiver: string,
totalValueSent: number
) => {
assertEqualBN(
await stableTokenCUSD.balanceOf(cusdSender),
startingBalanceCUSD - totalValueSent,
'cusdSender balance'
)
assertEqualBN(
await stableTokenCUSD.balanceOf(odisPayments.address),
totalValueSent,
'odisPayments.address balance'
)
assertEqualBN(
await odisPayments.totalPaidCUSD(odisPaymentReceiver),
totalValueSent,
'odisPaymentReceiver balance'
)
}

const valueApprovedForTransfer = 10
const receiver = accounts[2]

beforeEach(async () => {
await stableTokenCUSD.approve(odisPayments.address, valueApprovedForTransfer, {
from: sender,
})
assertEqualBN(await stableTokenCUSD.balanceOf(sender), startingBalanceCUSD)
})

it('should allow sender to make a payment on their behalf', async () => {
await odisPayments.payInCUSD(sender, valueApprovedForTransfer, { from: sender })
await checkStateCUSD(sender, sender, valueApprovedForTransfer)
})

it('should allow sender to make a payment for another account', async () => {
await odisPayments.payInCUSD(receiver, valueApprovedForTransfer, { from: sender })
await checkStateCUSD(sender, receiver, valueApprovedForTransfer)
})

it('should allow sender to make multiple payments to the contract', async () => {
const valueForSecondTransfer = 5
const valueForFirstTransfer = valueApprovedForTransfer - valueForSecondTransfer

await odisPayments.payInCUSD(sender, valueForFirstTransfer, { from: sender })
await checkStateCUSD(sender, sender, valueForFirstTransfer)

await odisPayments.payInCUSD(sender, valueForSecondTransfer, { from: sender })
await checkStateCUSD(sender, sender, valueApprovedForTransfer)
})

it('should emit the PaymentMade event', async () => {
const receipt = await odisPayments.payInCUSD(receiver, valueApprovedForTransfer, {
from: sender,
})
assertLogMatches2(receipt.logs[0], {
event: 'PaymentMade',
args: {
account: receiver,
valueInCUSD: valueApprovedForTransfer,
},
})
})

it('should revert if transfer fails', async () => {
await assertRevert(
odisPayments.payInCUSD(sender, valueApprovedForTransfer + 1, { from: sender })
)
assertEqualBN(await odisPayments.totalPaidCUSD(sender), 0)
})
})
})
2 changes: 1 addition & 1 deletion packages/sdk/contractkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"clean:all": "yarn clean && rm -rf src/generated",
"prepublishOnly": "yarn build",
"docs": "typedoc",
"test:reset": "yarn --cwd ../../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../../dev-utils/src/migration-override.json --upto 26",
"test:reset": "yarn --cwd ../../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../../dev-utils/src/migration-override.json --upto 27",
"test:livechain": "yarn --cwd ../../protocol devchain run-tar .tmp/devchain.tar.gz",
"test": "jest --runInBand",
"lint": "tslint -c tslint.json --project ."
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/identity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"build": "tsc -b .",
"clean": "tsc -b . --clean",
"docs": "typedoc",
"test:reset": "yarn --cwd ../../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../../dev-utils/src/migration-override.json --upto 26",
"test:reset": "yarn --cwd ../../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../../dev-utils/src/migration-override.json --upto 27",
"test:livechain": "yarn --cwd ../../protocol devchain run-tar .tmp/devchain.tar.gz",
"test": "jest --runInBand",
"lint": "tslint -c tslint.json --project .",
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/transactions-uri/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"build": "tsc -b .",
"clean": "tsc -b . --clean",
"docs": "typedoc",
"test:reset": "yarn --cwd ../../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../../dev-utils/src/migration-override.json --upto 26",
"test:reset": "yarn --cwd ../../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../../dev-utils/src/migration-override.json --upto 27",
"test:livechain": "yarn --cwd ../../protocol devchain run-tar .tmp/devchain.tar.gz",
"test": "jest --runInBand",
"lint": "tslint -c tslint.json --project .",
Expand Down