Skip to content
This repository was archived by the owner on May 22, 2023. It is now read-only.

Commit 62284c7

Browse files
authored
Merge pull request #470 from keep-network/roles-lookup
Allow grantee withdraw unbonded value from KeepBonding The previous construction allowed only the operator or token owner to withdraw the unbonded value. We were not allowing the token grantee to do that. There are four valid cases for unbonded value withdrawal: - operator withdraws, - token owner withdraws (when the delegated stake are tokens owned by the delegating party), - grantee withdraws (when the delegated stake are tokens granted to the delegating party), - managed grantee withdraws (when the delegated stake are tokens granted to the delegating party via `ManagedGrant` contract). All those cases are now supported in `KeepBonding` contract. The last case requires separate function since it is not possible to lookup `ManagedGrant` contract address in another way than by using an event.
2 parents 37de275 + 1e08c6a commit 62284c7

12 files changed

Lines changed: 342 additions & 39 deletions

solidity/contracts/KeepBonding.sol

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pragma solidity 0.5.17;
1616

1717
import "@keep-network/keep-core/contracts/KeepRegistry.sol";
1818
import "@keep-network/keep-core/contracts/TokenStaking.sol";
19+
import "@keep-network/keep-core/contracts/TokenGrant.sol";
20+
import "@keep-network/keep-core/contracts/libraries/RolesLookup.sol";
1921

2022
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
2123

@@ -24,13 +26,17 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol";
2426
/// @notice Contract holding deposits from keeps' operators.
2527
contract KeepBonding {
2628
using SafeMath for uint256;
29+
using RolesLookup for address payable;
2730

2831
// Registry contract with a list of approved factories (operator contracts).
2932
KeepRegistry internal registry;
3033

3134
// KEEP token staking contract.
3235
TokenStaking internal tokenStaking;
3336

37+
// KEEP token grant contract.
38+
TokenGrant internal tokenGrant;
39+
3440
// Unassigned value in wei deposited by operators.
3541
mapping(address => uint256) public unbondedValue;
3642

@@ -67,10 +73,16 @@ contract KeepBonding {
6773

6874
/// @notice Initializes Keep Bonding contract.
6975
/// @param registryAddress Keep registry contract address.
70-
/// @param tokenStakingAddress KEEP Token staking contract address.
71-
constructor(address registryAddress, address tokenStakingAddress) public {
76+
/// @param tokenStakingAddress KEEP token staking contract address.
77+
/// @param tokenGrantAddress KEEP token grant contract address.
78+
constructor(
79+
address registryAddress,
80+
address tokenStakingAddress,
81+
address tokenGrantAddress
82+
) public {
7283
registry = KeepRegistry(registryAddress);
7384
tokenStaking = TokenStaking(tokenStakingAddress);
85+
tokenGrant = TokenGrant(tokenGrantAddress);
7486
}
7587

7688
/// @notice Add the provided value to operator's pool available for bonding.
@@ -109,29 +121,47 @@ contract KeepBonding {
109121
}
110122

111123
/// @notice Withdraws amount from operator's value available for bonding.
112-
/// Can be called only by the operator or by the stake owner.
124+
/// Should not be used by grantee of managed grants. For this case,
125+
/// please use `withdrawAsManagedGrantee`.
126+
///
127+
/// This function can be called only by:
128+
/// - operator,
129+
/// - liquid, staked tokens owner (not a grant),
130+
/// - direct staked tokens grantee (not a managed grant).
131+
///
113132
/// @param amount Value to withdraw in wei.
114133
/// @param operator Address of the operator.
115134
function withdraw(uint256 amount, address operator) public {
116135
require(
117136
msg.sender == operator ||
118-
msg.sender == tokenStaking.ownerOf(operator),
137+
msg.sender.isTokenOwnerForOperator(operator, tokenStaking) ||
138+
msg.sender.isGranteeForOperator(operator, tokenGrant),
119139
"Only operator or the owner is allowed to withdraw bond"
120140
);
121141

142+
withdrawBond(amount, operator);
143+
}
144+
145+
/// @notice Withdraws amount from operator's value available for bonding.
146+
/// Can be called only by staked tokens managed grantee.
147+
/// @param amount Value to withdraw in wei.
148+
/// @param operator Address of the operator.
149+
/// @param managedGrant Address of the managed grant contract.
150+
function withdrawAsManagedGrantee(
151+
uint256 amount,
152+
address operator,
153+
address managedGrant
154+
) public {
122155
require(
123-
unbondedValue[operator] >= amount,
124-
"Insufficient unbonded value"
156+
msg.sender.isManagedGranteeForOperator(
157+
operator,
158+
managedGrant,
159+
tokenGrant
160+
),
161+
"Only grantee is allowed to withdraw bond"
125162
);
126163

127-
unbondedValue[operator] = unbondedValue[operator].sub(amount);
128-
129-
(bool success, ) = tokenStaking.beneficiaryOf(operator).call.value(
130-
amount
131-
)("");
132-
require(success, "Transfer failed");
133-
134-
emit UnbondedValueWithdrawn(operator, amount);
164+
withdrawBond(amount, operator);
135165
}
136166

137167
/// @notice Create bond for the given operator, holder, reference and amount.
@@ -329,4 +359,23 @@ contract KeepBonding {
329359
{
330360
return authorizedPools[_operator][_poolAddress];
331361
}
362+
363+
/// @notice Withdraws the provided amount from unbonded value of the
364+
/// provided operator to operator's beneficiary. If there is no enough
365+
/// unbonded value or the transfer failed, function fails.
366+
function withdrawBond(uint256 amount, address operator) internal {
367+
require(
368+
unbondedValue[operator] >= amount,
369+
"Insufficient unbonded value"
370+
);
371+
372+
unbondedValue[operator] = unbondedValue[operator].sub(amount);
373+
374+
(bool success, ) = tokenStaking.beneficiaryOf(operator).call.value(
375+
amount
376+
)("");
377+
require(success, "Transfer failed");
378+
379+
emit UnbondedValueWithdrawn(operator, amount);
380+
}
332381
}

solidity/migrations/2_deploy_contracts.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const BondedSortitionPoolFactory = artifacts.require(
1717
let {
1818
RandomBeaconAddress,
1919
TokenStakingAddress,
20+
TokenGrantAddress,
2021
RegistryAddress,
2122
} = require("./external-contracts")
2223

@@ -27,13 +28,21 @@ module.exports = async function (deployer) {
2728
TokenStakingStub = artifacts.require("TokenStakingStub")
2829
TokenStakingAddress = (await TokenStakingStub.new()).address
2930

31+
TokenGrantStub = artifacts.require("TokenGrantStub")
32+
TokenGrantAddress = (await TokenGrantStub.new()).address
33+
3034
RandomBeaconStub = artifacts.require("RandomBeaconStub")
3135
RandomBeaconAddress = (await RandomBeaconStub.new()).address
3236

3337
RegistryAddress = (await deployer.deploy(KeepRegistry)).address
3438
}
3539

36-
await deployer.deploy(KeepBonding, RegistryAddress, TokenStakingAddress)
40+
await deployer.deploy(
41+
KeepBonding,
42+
RegistryAddress,
43+
TokenStakingAddress,
44+
TokenGrantAddress
45+
)
3746

3847
await deployer.deploy(BondedECDSAKeep)
3948

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// Addresses of externally deployed smart contracts
22
const RegistryAddress = "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
33
const TokenStakingAddress = "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
4-
const RandomBeaconAddress = "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
5-
const TBTCSystemAddress = "0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
4+
const TokenGrantAddress = "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
5+
const RandomBeaconAddress = "0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
6+
const TBTCSystemAddress = "0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
67

78
module.exports = {
89
RegistryAddress,
910
TokenStakingAddress,
11+
TokenGrantAddress,
1012
RandomBeaconAddress,
1113
TBTCSystemAddress,
1214
}

solidity/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solidity/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
},
3333
"homepage": "https://github.com/keep-network/keep-ecdsa",
3434
"dependencies": {
35-
"@keep-network/keep-core": "1.1.2",
35+
"@keep-network/keep-core": "1.2.3-pre.0",
3636
"@keep-network/sortition-pools": "1.0.0",
3737
"@openzeppelin/upgrades": "^2.7.2",
3838
"openzeppelin-solidity": "2.3.0",

solidity/scripts/lcl-provision-external-contracts.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ REGISTRY_PROPERTY="RegistryAddress"
1818
TOKEN_STAKING_CONTRACT_DATA="TokenStaking.json"
1919
TOKEN_STAKING_PROPERTY="TokenStakingAddress"
2020

21+
TOKEN_GRANT_CONTRACT_DATA="TokenGrant.json"
22+
TOKEN_GRANT_PROPERTY="TokenGrantAddress"
23+
2124
RANDOM_BEACON_CONTRACT_DATA="KeepRandomBeaconService.json"
2225
RANDOM_BEACON_PROPERTY="RandomBeaconAddress"
2326

@@ -61,6 +64,21 @@ function fetch_token_staking_contract_address() {
6164
fi
6265
}
6366

67+
function fetch_token_grant_contract_address() {
68+
echo "Fetching value for ${TOKEN_GRANT_PROPERTY}..."
69+
70+
local contractDataPath=$(realpath $KEEP_CORE_SOL_ARTIFACTS_PATH/$TOKEN_GRANT_CONTRACT_DATA)
71+
local ADDRESS=$(cat ${contractDataPath} | jq "${JSON_QUERY}" | tr -d '"')
72+
73+
if [[ !($ADDRESS =~ $ADDRESS_REGEXP) ]]; then
74+
echo "Invalid address: ${ADDRESS}"
75+
FAILED=true
76+
else
77+
echo "Found value for ${TOKEN_GRANT_PROPERTY} = ${ADDRESS}"
78+
sed -i -e "/${TOKEN_GRANT_PROPERTY}/s/${SED_SUBSTITUTION_REGEXP}/\"${ADDRESS}\"/" $DESTINATION_FILE
79+
fi
80+
}
81+
6482
function fetch_random_beacon_contract_address() {
6583
echo "Fetching value for ${RANDOM_BEACON_PROPERTY}..."
6684

@@ -78,6 +96,7 @@ function fetch_random_beacon_contract_address() {
7896

7997
fetch_registry_contract_address
8098
fetch_token_staking_contract_address
99+
fetch_token_grant_contract_address
81100
fetch_random_beacon_contract_address
82101

83102
if $FAILED; then

solidity/test/BondedECDSAKeepFactoryTest.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const BondedECDSAKeepFactoryStub = artifacts.require(
1212
)
1313
const KeepBonding = artifacts.require("KeepBonding")
1414
const TokenStakingStub = artifacts.require("TokenStakingStub")
15+
const TokenGrantStub = artifacts.require("TokenGrantStub")
1516
const BondedSortitionPool = artifacts.require("BondedSortitionPool")
1617
const BondedSortitionPoolFactory = artifacts.require(
1718
"BondedSortitionPoolFactory"
@@ -28,6 +29,7 @@ const expect = chai.expect
2829
contract("BondedECDSAKeepFactory", async (accounts) => {
2930
let registry
3031
let tokenStaking
32+
let tokenGrant
3133
let keepFactory
3234
let bondedSortitionPoolFactory
3335
let keepBonding
@@ -1178,9 +1180,11 @@ contract("BondedECDSAKeepFactory", async (accounts) => {
11781180
registry = await KeepRegistry.new()
11791181
bondedSortitionPoolFactory = await BondedSortitionPoolFactory.new()
11801182
tokenStaking = await TokenStakingStub.new()
1183+
tokenGrant = await TokenGrantStub.new()
11811184
keepBonding = await KeepBonding.new(
11821185
registry.address,
1183-
tokenStaking.address
1186+
tokenStaking.address,
1187+
tokenGrant.address
11841188
)
11851189
randomBeacon = accounts[1]
11861190
const bondedECDSAKeepMasterContract = await BondedECDSAKeep.new()
@@ -1477,7 +1481,12 @@ contract("BondedECDSAKeepFactory", async (accounts) => {
14771481
registry = await KeepRegistry.new()
14781482
bondedSortitionPoolFactory = await BondedSortitionPoolFactory.new()
14791483
tokenStaking = await TokenStakingStub.new()
1480-
keepBonding = await KeepBonding.new(registry.address, tokenStaking.address)
1484+
tokenGrant = await TokenGrantStub.new()
1485+
keepBonding = await KeepBonding.new(
1486+
registry.address,
1487+
tokenStaking.address,
1488+
tokenGrant.address
1489+
)
14811490
randomBeacon = await RandomBeaconStub.new()
14821491
const bondedECDSAKeepMasterContract = await BondedECDSAKeep.new()
14831492
keepFactory = await BondedECDSAKeepFactoryStub.new(

solidity/test/BondedECDSAKeepIntegrationTest.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const BondedECDSAKeepFactoryStub = artifacts.require(
1111
)
1212
const KeepBonding = artifacts.require("KeepBonding")
1313
const TokenStaking = artifacts.require("TokenStaking")
14+
const TokenGrant = artifacts.require("TokenGrant")
1415
const BondedSortitionPool = artifacts.require("BondedSortitionPool")
1516
const BondedSortitionPoolFactory = artifacts.require(
1617
"BondedSortitionPoolFactory"
@@ -27,6 +28,7 @@ const expect = chai.expect
2728
contract("BondedECDSAKeepFactory", async (accounts) => {
2829
let keepToken
2930
let tokenStaking
31+
let tokenGrant
3032
let keepFactory
3133
let bondedSortitionPoolFactory
3234
let keepBonding
@@ -245,8 +247,13 @@ contract("BondedECDSAKeepFactory", async (accounts) => {
245247
initializationPeriod,
246248
undelegationPeriod
247249
)
250+
tokenGrant = await TokenGrant.new(keepToken.address)
248251

249-
keepBonding = await KeepBonding.new(registry.address, tokenStaking.address)
252+
keepBonding = await KeepBonding.new(
253+
registry.address,
254+
tokenStaking.address,
255+
tokenGrant.address
256+
)
250257
randomBeacon = await RandomBeaconStub.new()
251258
const bondedECDSAKeepMasterContract = await BondedECDSAKeep.new()
252259
keepFactory = await BondedECDSAKeepFactoryStub.new(

solidity/test/BondedECDSAKeepTest.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const TestToken = artifacts.require("TestToken")
1616
const KeepBonding = artifacts.require("KeepBonding")
1717
const TestEtherReceiver = artifacts.require("TestEtherReceiver")
1818
const TokenStakingStub = artifacts.require("TokenStakingStub")
19+
const TokenGrantStub = artifacts.require("TokenGrantStub")
1920
const BondedECDSAKeepCloneFactory = artifacts.require(
2021
"BondedECDSAKeepCloneFactory"
2122
)
@@ -42,6 +43,7 @@ contract("BondedECDSAKeep", (accounts) => {
4243
let registry
4344
let keepBonding
4445
let tokenStaking
46+
let tokenGrant
4547
let bondedECDSAKeepStubMaster
4648
let keep
4749
let factoryStub
@@ -87,7 +89,12 @@ contract("BondedECDSAKeep", (accounts) => {
8789
before(async () => {
8890
registry = await KeepRegistry.new()
8991
tokenStaking = await TokenStakingStub.new()
90-
keepBonding = await KeepBonding.new(registry.address, tokenStaking.address)
92+
tokenGrant = await TokenGrantStub.new()
93+
keepBonding = await KeepBonding.new(
94+
registry.address,
95+
tokenStaking.address,
96+
tokenGrant.address
97+
)
9198
bondedECDSAKeepStubMaster = await BondedECDSAKeepStub.new()
9299
factoryStub = await BondedECDSAKeepCloneFactory.new(
93100
bondedECDSAKeepStubMaster.address

0 commit comments

Comments
 (0)