Skip to content

Commit 4c70a63

Browse files
authored
LOAS: add revoke function (#45)
* LOAS: add revoke function * LOAS: remove original claimer param from revoke
1 parent e5cbb3a commit 4c70a63

File tree

5 files changed

+284
-7
lines changed

5 files changed

+284
-7
lines changed

.solhint.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"compiler-version": ["error", "^0.8.0"],
55
"func-param-name-mixedcase": "error",
66
"modifier-name-mixedcase": "error",
7-
"private-vars-leading-underscore": "error"
7+
"private-vars-leading-underscore": "error",
8+
"avoid-low-level-calls": "off"
89
}
910
}

artifacts/contracts/token/LOAS.sol/LOAS.json

+60-2
Large diffs are not rendered by default.

contracts/token/LOAS.sol

+48-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ error InvalidClaimPeriod();
1515
// OAS is zero.
1616
error NoAmount();
1717

18+
// Something not found.
19+
error NotFound();
20+
1821
// Invalid minter address.
1922
error InvalidMinter();
2023

24+
// Invalid revoker address.
25+
error InvalidRevoker();
26+
2127
// Over claimable OAS.
2228
error OverAmount();
2329

@@ -46,6 +52,7 @@ contract LOAS is ERC20 {
4652
uint64 since;
4753
uint64 until;
4854
address from;
55+
uint256 revoked;
4956
}
5057

5158
/**********************
@@ -62,6 +69,7 @@ contract LOAS is ERC20 {
6269
event Mint(address indexed to, uint256 amount, uint256 since, uint256 until);
6370
event Claim(address indexed holder, uint256 amount);
6471
event Renounce(address indexed holder, uint256 amount);
72+
event Revoke(address indexed original, address indexed holder, uint256 amount);
6573
event Allow(address indexed original, address indexed transferable);
6674

6775
/***************
@@ -91,7 +99,7 @@ contract LOAS is ERC20 {
9199
if (msg.value == 0) revert NoAmount();
92100

93101
_mint(to, msg.value);
94-
claimInfo[to] = ClaimInfo(msg.value, 0, since, until, msg.sender);
102+
claimInfo[to] = ClaimInfo(msg.value, 0, since, until, msg.sender, 0);
95103
originalClaimer[to] = to;
96104

97105
emit Mint(to, msg.value, since, until);
@@ -149,7 +157,8 @@ contract LOAS is ERC20 {
149157
if (amount == 0) revert NoAmount();
150158

151159
ClaimInfo storage originalClaimInfo = claimInfo[originalClaimer[msg.sender]];
152-
if (amount > originalClaimInfo.amount - originalClaimInfo.claimed) revert OverAmount();
160+
uint256 remainingAmount = originalClaimInfo.amount - originalClaimInfo.claimed - originalClaimInfo.revoked;
161+
if (amount > remainingAmount) revert OverAmount();
153162

154163
_burn(msg.sender, amount);
155164
(bool success, ) = originalClaimInfo.from.call{ value: amount }("");
@@ -158,6 +167,42 @@ contract LOAS is ERC20 {
158167
emit Renounce(originalClaimer[msg.sender], amount);
159168
}
160169

170+
/**
171+
* Revoke the LOAS from the holder.
172+
* As the default behavior, only the locked amount can be revoked.
173+
* otherwise, the amount can be specified.
174+
* @param holder Address of the holder.
175+
* @param amount_ Amount of the LOAS.
176+
*/
177+
function revoke(address holder, uint256 amount_) external {
178+
address original = originalClaimer[holder];
179+
if (original == address(0)) revert NotFound();
180+
// Only the minter can revoke the LOAS.
181+
ClaimInfo storage originalClaimInfo = claimInfo[original];
182+
if (originalClaimInfo.from != msg.sender) revert InvalidRevoker();
183+
184+
// Determine the amount to revoke.
185+
uint256 amount = amount_;
186+
if (amount == 0) {
187+
// As a default, revoke only the locked amount.
188+
uint256 remainingAmount = originalClaimInfo.amount - originalClaimInfo.claimed - originalClaimInfo.revoked;
189+
uint256 currentClaimableOAS = getClaimableOAS(original) - originalClaimInfo.claimed;
190+
if (remainingAmount <= currentClaimableOAS) revert NoAmount(); // Sanity check
191+
amount = remainingAmount - currentClaimableOAS;
192+
}
193+
194+
// Check over amount.
195+
if (balanceOf(holder) < amount) revert OverAmount();
196+
197+
// Revoke the LOAS.
198+
originalClaimInfo.revoked += amount;
199+
_burn(holder, amount);
200+
(bool success, ) = msg.sender.call{ value: amount }("");
201+
if (!success) revert TransferFailed();
202+
203+
emit Revoke(original, holder, amount);
204+
}
205+
161206
/**
162207
* Bulk transfer
163208
* @param tos List of receipient address.
@@ -223,7 +268,7 @@ contract LOAS is ERC20 {
223268
function _beforeTokenTransfer(
224269
address from,
225270
address to,
226-
uint256 amount
271+
uint256 /*amount*/
227272
) internal view override {
228273
if (from == address(0) || to == address(0)) return;
229274
if (originalClaimer[from] == originalClaimer[to]) return;

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "oasys-genesis-contract",
3-
"version": "1.6.2",
3+
"version": "1.7.0",
44
"description": "The genesis contracts of Oasys Blockchain",
55
"main": "index.js",
66
"scripts": {

test/token/LOAS.spec.ts

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { ethers, network } from 'hardhat'
2+
import { Contract } from 'ethers'
3+
import { SignerWithAddress as Account } from '@nomiclabs/hardhat-ethers/signers'
4+
import { toWei, fromWei } from 'web3-utils'
5+
import { expect } from 'chai'
6+
7+
const getTimestamp = (dates: string): number => {
8+
const date = new Date(dates)
9+
return date.getTime() / 1000
10+
}
11+
12+
const setBlockTimestamp = async (dates: string) => {
13+
await network.provider.send('evm_setNextBlockTimestamp', [getTimestamp(dates)])
14+
}
15+
16+
describe('LOAS', () => {
17+
let loas: Contract
18+
let genesis: Account
19+
let originalClaimer: Account
20+
let allowedClaimer: Account
21+
let invalidClaimer: Account
22+
23+
before(async () => {
24+
const accounts = await ethers.getSigners()
25+
genesis = accounts[1]
26+
originalClaimer = accounts[2]
27+
allowedClaimer = accounts[3]
28+
invalidClaimer = accounts[4]
29+
})
30+
31+
beforeEach(async () => {
32+
await network.provider.send('hardhat_reset')
33+
loas = await (await ethers.getContractFactory('LOAS')).deploy()
34+
})
35+
36+
describe('revoke()', async () => {
37+
const expectBalance = async (user: Account, expOAS: number, expLOAS: number) => {
38+
const actualOAS = fromWei((await user.getBalance()).toString())
39+
const actualLOAS = fromWei((await loas.balanceOf(user.address)).toString())
40+
expect(actualOAS).to.match(new RegExp(`^${expOAS + 10000}`))
41+
expect(actualLOAS).to.match(new RegExp(`^${expLOAS}`))
42+
}
43+
const expectTotalSupply = async (exp: number) => {
44+
const actual = fromWei((await loas.totalSupply()).toString())
45+
expect(actual).to.match(new RegExp(`^${exp}`))
46+
}
47+
48+
it('revoke all', async () => {
49+
// initial balance.
50+
await expectBalance(genesis, 0, 0)
51+
await expectBalance(originalClaimer, 0, 0)
52+
await expectTotalSupply(0)
53+
54+
// minting.
55+
await setBlockTimestamp('2100/01/01')
56+
await loas
57+
.connect(genesis)
58+
.mint(originalClaimer.address, getTimestamp('2100/07/01'), getTimestamp('2100/12/31'), {
59+
value: toWei('100'),
60+
})
61+
62+
// after minted.
63+
await expectBalance(originalClaimer, 0, 100)
64+
await expectTotalSupply(100)
65+
66+
// 1 month elapsed.
67+
await setBlockTimestamp('2100/07/31')
68+
await loas.connect(originalClaimer).claim(toWei('16'))
69+
await expectBalance(originalClaimer, 16, 84)
70+
await expectTotalSupply(84)
71+
72+
// Fail by non-existing holder
73+
let tx = loas.connect(originalClaimer).revoke(invalidClaimer.address, toWei('84'))
74+
await expect(tx).to.revertedWith('NotFound()')
75+
76+
// Fail by invalid revoker.
77+
tx = loas.connect(originalClaimer).revoke(originalClaimer.address, toWei('84'))
78+
await expect(tx).to.revertedWith('InvalidRevoker()')
79+
80+
// Revoke all.
81+
await expect(await loas.connect(genesis).revoke(originalClaimer.address, toWei('84')))
82+
.to.emit(loas, 'Revoke')
83+
.withArgs(originalClaimer.address, originalClaimer.address, toWei('84'))
84+
await expectBalance(genesis, -16, 0)
85+
await expectTotalSupply(0)
86+
})
87+
88+
it('revoke locked only', async () => {
89+
// minting.
90+
await setBlockTimestamp('2100/01/01')
91+
await loas
92+
.connect(genesis)
93+
.mint(originalClaimer.address, getTimestamp('2100/07/01'), getTimestamp('2100/12/31'), {
94+
value: toWei('100'),
95+
})
96+
97+
// after minted.
98+
await expectBalance(originalClaimer, 0, 100)
99+
await expectTotalSupply(100)
100+
101+
// 1 month elapsed.
102+
await setBlockTimestamp('2100/07/31')
103+
104+
// Revoke locked only.
105+
await loas.connect(genesis).revoke(originalClaimer.address, 0)
106+
let actualOAS = fromWei((await genesis.getBalance()).toString())
107+
expect(Math.ceil(Number(actualOAS))).to.equal(10000-100+84)
108+
let supply = fromWei((await loas.totalSupply()).toString())
109+
expect(Math.ceil(Number(supply))).to.equal(17)
110+
111+
// Claim.
112+
await loas.connect(originalClaimer).claim(toWei('16'))
113+
await expectBalance(originalClaimer, 16, 0)
114+
supply = fromWei((await loas.totalSupply()).toString())
115+
expect(Math.ceil(Number(supply))).to.equal(1) // left less than 1 OAS.
116+
117+
// 2 month elapsed.
118+
await setBlockTimestamp('2100/08/31')
119+
120+
// Claim left.
121+
const left = await loas.balanceOf(originalClaimer.address)
122+
await loas.connect(originalClaimer).claim(left)
123+
supply = fromWei((await loas.totalSupply()).toString())
124+
expect(Math.ceil(Number(supply))).to.equal(0)
125+
})
126+
127+
it('revoke splited LOAS', async () => {
128+
await setBlockTimestamp('2100/01/01')
129+
await loas
130+
.connect(genesis)
131+
.mint(originalClaimer.address, getTimestamp('2100/07/01'), getTimestamp('2100/12/31'), {
132+
value: toWei('100', 'ether'),
133+
})
134+
135+
await expectBalance(allowedClaimer, 0, 0)
136+
137+
// 2 month elapsed.
138+
await setBlockTimestamp('2100/08/31')
139+
140+
// transfer.
141+
await loas.connect(genesis)['allow(address,address)'](originalClaimer.address, allowedClaimer.address)
142+
await loas.connect(originalClaimer)['transfer(address,uint256)'](allowedClaimer.address, toWei('50'))
143+
await expectBalance(originalClaimer, 0, 50)
144+
await expectBalance(allowedClaimer, 0, 50)
145+
await expectTotalSupply(100)
146+
147+
// try to revoke original claimer, but it should fail as the locked LOAS is over than original claimer's balance.
148+
const tx = loas.connect(genesis).revoke(originalClaimer.address, 0)
149+
await expect(tx).to.revertedWith('OverAmount()')
150+
151+
// revoke all the original claimer's balance.
152+
await loas.connect(genesis).revoke(originalClaimer.address, toWei('50'))
153+
await expectBalance(originalClaimer, 0, 0)
154+
await expectBalance(genesis, -50, 0)
155+
await expectTotalSupply(50)
156+
157+
// revoke only locked LOAS from allowed claimer.
158+
await loas.connect(genesis).revoke(allowedClaimer.address, 0)
159+
let actualOAS = fromWei((await genesis.getBalance()).toString())
160+
expect(Math.ceil(Number(actualOAS))).to.equal(10000-100+50+17)
161+
let supply = fromWei((await loas.totalSupply()).toString())
162+
expect(Math.ceil(Number(supply))).to.equal(34)
163+
const clainInfo = await loas.claimInfo(originalClaimer.address)
164+
expect(Math.ceil(Number(fromWei((clainInfo.revoked).toString())))).to.equal(50+17)
165+
166+
// claim all the left amount
167+
const left = await loas.balanceOf(allowedClaimer.address)
168+
await loas.connect(allowedClaimer).claim(left)
169+
supply = fromWei((await loas.totalSupply()).toString())
170+
expect(Math.ceil(Number(supply))).to.equal(0)
171+
})
172+
})
173+
})

0 commit comments

Comments
 (0)