Skip to content

Commit

Permalink
Add Specific Retirement for Toucan Protocol (KlimaDAO#11)
Browse files Browse the repository at this point in the history
Replace `redeemAuto` with `redeemAuto2` to save on gas, as well as implement check
to ensure that the `retire` function is only called on TCO2s actually redeemed.

Add methods to selectively choose and retire TCO2s using the aggregator. This accounts
for any redemption fees incurred so that the desired number of tons will be retired.

Update the `getNeededBuyAmount` function across all helpers to allow consistent switching
between factoring in a redemption fee or just the AMM fees.
  • Loading branch information
cujowolf authored Apr 12, 2022
1 parent 51a5a6f commit d0a98d5
Show file tree
Hide file tree
Showing 7 changed files with 551 additions and 70 deletions.
3 changes: 2 additions & 1 deletion contracts/interfaces/IRetireBridgeCommon.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ interface IRetireBridgeCommon {
function getNeededBuyAmount(
address _sourceToken,
address _poolToken,
uint256 _poolAmount
uint256 _poolAmount,
bool _retireSpecific
) external view returns (uint256, uint256);

function getSwapPath(address _sourceToken, address _poolToken)
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/IRetireMossCarbon.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface IRetireMossCarbon {
function getNeededBuyAmount(
address _sourceToken,
address _poolToken,
uint256 _poolAmount
uint256 _poolAmount,
bool _retireSpecific
) external view returns (uint256, uint256);
}
15 changes: 14 additions & 1 deletion contracts/interfaces/IRetireToucanCarbon.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,22 @@ interface IRetireToucanCarbon {
address _retiree
) external;

function retireToucanSpecific(
address _sourceToken,
address _poolToken,
uint256 _amount,
bool _amountInCarbon,
address _beneficiaryAddress,
string memory _beneficiaryString,
string memory _retirementMessage,
address _retiree,
address[] memory _carbonList
) external;

function getNeededBuyAmount(
address _sourceToken,
address _poolToken,
uint256 _poolAmount
uint256 _poolAmount,
bool _retireSpecific
) external view returns (uint256, uint256);
}
13 changes: 11 additions & 2 deletions contracts/interfaces/IToucanPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
pragma solidity ^0.8.0;

interface IToucanPool {
function getScoredTCO2s() external view returns (address[] memory);
function redeemAuto2(uint256 amount)
external
returns (address[] memory tco2s, uint256[] memory amounts);

function redeemAuto(uint256 amount) external;
function redeemMany(address[] calldata erc20s, uint256[] calldata amounts)
external;

function feeRedeemPercentageInBase() external pure returns (uint256);

function feeRedeemDivider() external pure returns (uint256);

function redeemFeeExemptedAddresses(address) external view returns (bool);
}
311 changes: 263 additions & 48 deletions contracts/retirement/KlimaRetirementAggregator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ contract KlimaRetirementAggregator is
event PoolRemoved(address poolToken);
event BridgeHelperUpdated(uint256 bridgeID, address helper);

/** === Non Specific Auto Retirements */

/**
* @notice This function will retire a carbon pool token that is held
* in the caller's wallet. Depending on the pool provided the appropriate
Expand Down Expand Up @@ -153,54 +155,6 @@ contract KlimaRetirementAggregator is
);
}

/**
* @notice This function calls the appropriate helper for a pool token and
* returns the total amount in source tokens needed to perform the transaction.
* Any swap slippage buffers and fees are included in the return value.
*
* @param _sourceToken The contract address of the token being supplied.
* @param _poolToken The contract address of the pool token being retired.
* @param _amount The amount being supplied. Expressed in either the total
* carbon to offset or the total source to spend. See _amountInCarbon.
* @param _amountInCarbon Bool indicating if _amount is in carbon or source.
* @return Returns both the source amount and carbon amount as a result of swaps.
*/
function getSourceAmount(
address _sourceToken,
address _poolToken,
uint256 _amount,
bool _amountInCarbon
) public view returns (uint256, uint256) {
uint256 sourceAmount;
uint256 carbonAmount = _amount;

if (_amountInCarbon) {
(sourceAmount, ) = IRetireBridgeCommon(
bridgeHelper[poolBridge[_poolToken]]
).getNeededBuyAmount(_sourceToken, _poolToken, _amount);
if (_sourceToken == wsKLIMA) {
sourceAmount = IwsKLIMA(wsKLIMA).sKLIMATowKLIMA(sourceAmount);
}
} else {
sourceAmount = _amount;

address poolRouter = IRetireBridgeCommon(
bridgeHelper[poolBridge[_poolToken]]
).poolRouter(_poolToken);

address[] memory path = IRetireBridgeCommon(
bridgeHelper[poolBridge[_poolToken]]
).getSwapPath(_sourceToken, _poolToken);

uint256[] memory amountsOut = IUniswapV2Router02(poolRouter)
.getAmountsOut(_amount, path);

carbonAmount = amountsOut[path.length - 1];
}

return (sourceAmount, carbonAmount);
}

/**
* @notice Internal function that checks to make sure the needed source tokens
* have been transferred to this contract, then calls the retirement function
Expand Down Expand Up @@ -269,6 +223,267 @@ contract KlimaRetirementAggregator is
}
}

/** === Specific offset selection retirements === */

/**
* @notice This function will retire a carbon pool token that is held
* in the caller's wallet. Depending on the pool provided the appropriate
* retirement helper will be used as defined in the bridgeHelper mapping.
* If a token other than the pool is provided then the helper will attempt
* to swap to the appropriate pool and then retire.
*
* @param _sourceToken The contract address of the token being supplied.
* @param _poolToken The contract address of the pool token being retired.
* @param _amount The amount being supplied. Expressed in either the total
* carbon to offset or the total source to spend. See _amountInCarbon.
* @param _amountInCarbon Bool indicating if _amount is in carbon or source.
* @param _beneficiaryAddress Address of the beneficiary of the retirement.
* @param _beneficiaryString String representing the beneficiary. A name perhaps.
* @param _retirementMessage Specific message relating to this retirement event.
*/
function retireCarbonSpecific(
address _sourceToken,
address _poolToken,
uint256 _amount,
bool _amountInCarbon,
address _beneficiaryAddress,
string memory _beneficiaryString,
string memory _retirementMessage,
address[] memory _carbonList
) public {
//require(isPoolToken[_poolToken], "Pool Token Not Accepted.");

(uint256 sourceAmount, ) = getSourceAmountSpecific(
_sourceToken,
_poolToken,
_amount,
_amountInCarbon
);

IERC20Upgradeable(_sourceToken).safeTransferFrom(
_msgSender(),
address(this),
sourceAmount
);

_retireCarbonSpecific(
_sourceToken,
_poolToken,
_amount,
_amountInCarbon,
_beneficiaryAddress,
_beneficiaryString,
_retirementMessage,
_msgSender(),
_carbonList
);
}

function retireCarbonSpecificFrom(
address _initiator,
address _sourceToken,
address _poolToken,
uint256 _amount,
bool _amountInCarbon,
address _beneficiaryAddress,
string memory _beneficiaryString,
string memory _retirementMessage,
address[] memory _carbonList
) public {
address retiree = _initiator;

_retireCarbonSpecific(
_sourceToken,
_poolToken,
_amount,
_amountInCarbon,
_beneficiaryAddress,
_beneficiaryString,
_retirementMessage,
retiree,
_carbonList
);
}

/**
* @notice Internal function that checks to make sure the needed source tokens
* have been transferred to this contract, then calls the retirement function
* on the bridge's specific helper contract.
*
* @param _sourceToken The contract address of the token being supplied.
* @param _poolToken The contract address of the pool token being retired.
* @param _amount The amount being supplied. Expressed in either the total
* carbon to offset or the total source to spend. See _amountInCarbon.
* @param _amountInCarbon Bool indicating if _amount is in carbon or source.
* @param _beneficiaryAddress Address of the beneficiary of the retirement.
* @param _beneficiaryString String representing the beneficiary. A name perhaps.
* @param _retirementMessage Specific message relating to this retirement event.
* @param _retiree Address of the initiator where source tokens originated.
*/
function _retireCarbonSpecific(
address _sourceToken,
address _poolToken,
uint256 _amount,
bool _amountInCarbon,
address _beneficiaryAddress,
string memory _beneficiaryString,
string memory _retirementMessage,
address _retiree,
address[] memory _carbonList
) internal {
require(isPoolToken[_poolToken], "Pool Token Not Accepted.");
// Only Toucan and C3 currently allow specific retirement.
require(
poolBridge[_poolToken] == 1 || poolBridge[_poolToken] == 2,
"Pool does not allow specific."
);

_prepareRetireSpecific(
_sourceToken,
_poolToken,
_amount,
_amountInCarbon
);

if (poolBridge[_poolToken] == 0) {
// Reserve for possible future use.
} else if (poolBridge[_poolToken] == 1) {
IRetireToucanCarbon(bridgeHelper[1]).retireToucanSpecific(
_sourceToken,
_poolToken,
_amount,
_amountInCarbon,
_beneficiaryAddress,
_beneficiaryString,
_retirementMessage,
_retiree,
_carbonList
);
}
}

function _prepareRetireSpecific(
address _sourceToken,
address _poolToken,
uint256 _amount,
bool _amountInCarbon
) internal {
(uint256 sourceAmount, ) = getSourceAmountSpecific(
_sourceToken,
_poolToken,
_amount,
_amountInCarbon
);

require(
IERC20Upgradeable(_sourceToken).balanceOf(address(this)) ==
sourceAmount,
"Source tokens not transferred."
);

IERC20Upgradeable(_sourceToken).safeIncreaseAllowance(
bridgeHelper[poolBridge[_poolToken]],
sourceAmount
);
}

/** === External views and helpful functions === */

/**
* @notice This function calls the appropriate helper for a pool token and
* returns the total amount in source tokens needed to perform the transaction.
* Any swap slippage buffers and fees are included in the return value.
*
* @param _sourceToken The contract address of the token being supplied.
* @param _poolToken The contract address of the pool token being retired.
* @param _amount The amount being supplied. Expressed in either the total
* carbon to offset or the total source to spend. See _amountInCarbon.
* @param _amountInCarbon Bool indicating if _amount is in carbon or source.
* @return Returns both the source amount and carbon amount as a result of swaps.
*/
function getSourceAmount(
address _sourceToken,
address _poolToken,
uint256 _amount,
bool _amountInCarbon
) public view returns (uint256, uint256) {
uint256 sourceAmount;
uint256 carbonAmount = _amount;

if (_amountInCarbon) {
(sourceAmount, ) = IRetireBridgeCommon(
bridgeHelper[poolBridge[_poolToken]]
).getNeededBuyAmount(_sourceToken, _poolToken, _amount, false);
if (_sourceToken == wsKLIMA) {
sourceAmount = IwsKLIMA(wsKLIMA).sKLIMATowKLIMA(sourceAmount);
}
} else {
sourceAmount = _amount;

address poolRouter = IRetireBridgeCommon(
bridgeHelper[poolBridge[_poolToken]]
).poolRouter(_poolToken);

address[] memory path = IRetireBridgeCommon(
bridgeHelper[poolBridge[_poolToken]]
).getSwapPath(_sourceToken, _poolToken);

uint256[] memory amountsOut = IUniswapV2Router02(poolRouter)
.getAmountsOut(_amount, path);

carbonAmount = amountsOut[path.length - 1];
}

return (sourceAmount, carbonAmount);
}

/**
* @notice Same as getSourceAmount, but factors in the redemption fee
* for specific retirements.
*
* @param _sourceToken The contract address of the token being supplied.
* @param _poolToken The contract address of the pool token being retired.
* @param _amount The amount being supplied. Expressed in either the total
* carbon to offset or the total source to spend. See _amountInCarbon.
* @param _amountInCarbon Bool indicating if _amount is in carbon or source.
* @return Returns both the source amount and carbon amount as a result of swaps.
*/
function getSourceAmountSpecific(
address _sourceToken,
address _poolToken,
uint256 _amount,
bool _amountInCarbon
) public view returns (uint256, uint256) {
uint256 sourceAmount;
uint256 carbonAmount = _amount;

if (_amountInCarbon) {
(sourceAmount, ) = IRetireBridgeCommon(
bridgeHelper[poolBridge[_poolToken]]
).getNeededBuyAmount(_sourceToken, _poolToken, _amount, true);
if (_sourceToken == wsKLIMA) {
sourceAmount = IwsKLIMA(wsKLIMA).sKLIMATowKLIMA(sourceAmount);
}
} else {
sourceAmount = _amount;

address poolRouter = IRetireBridgeCommon(
bridgeHelper[poolBridge[_poolToken]]
).poolRouter(_poolToken);

address[] memory path = IRetireBridgeCommon(
bridgeHelper[poolBridge[_poolToken]]
).getSwapPath(_sourceToken, _poolToken);

uint256[] memory amountsOut = IUniswapV2Router02(poolRouter)
.getAmountsOut(_amount, path);

carbonAmount = amountsOut[path.length - 1];
}

return (sourceAmount, carbonAmount);
}

/**
* @notice Allow the contract owner to update Klima protocol addresses
* resulting from possible migrations.
Expand Down
Loading

0 comments on commit d0a98d5

Please sign in to comment.